Skip to content

Commit

Permalink
Message representation (#7)
Browse files Browse the repository at this point in the history
* Added forgotten test case for reconnection on graceful shutdown.
* Added parent class for all message types implementing repr/str/eq
* Fixed possible issue with VIN being either str or bytes
  • Loading branch information
jacobschaer committed Sep 27, 2021
1 parent f0bb64d commit f26a6b3
Show file tree
Hide file tree
Showing 2 changed files with 279 additions and 164 deletions.
143 changes: 118 additions & 25 deletions doipclient/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,42 @@
# Quoted descriptions were copied or paraphrased from ISO-13400-2-2019 (E).


class ReservedMessage:
class DoIPMessage:
"""Base class for DoIP messages implementing common features like comparison,
and representation"""

def __repr__(self):
formatted_field_values = []
for field in self._fields:
value = getattr(self, "_" + field)
if type(value) == str:
formatted_field_values.append(f'"{value}"')
else:
formatted_field_values.append(str(value))
args = ", ".join(formatted_field_values)
classname = type(self).__name__
return f"{classname}({args})"

def __str__(self):
formatted_field_values = []
for field in self._fields:
value = getattr(self, field)
if type(value) == str:
formatted_field_values.append(f'{field}: "{value}"')
else:
formatted_field_values.append(f"{field} : {str(value)}")
args = ", ".join(formatted_field_values)
classname = type(self).__name__
if args:
return f"{classname} (0x{self.payload_type:X}): {{ {args} }}"
else:
return f"{classname} (0x{self.payload_type:X})"

def __eq__(self, other):
return (type(self) == type(other)) and (self.pack() == other.pack())


class ReservedMessage(DoIPMessage):
"""DoIP message whose payload ID is reserved either for manufacturer use or future
expansion of DoIP protocol"""

Expand All @@ -15,6 +50,8 @@ def unpack(cls, payload_type, payload_bytes, payload_length):
def pack(self):
self._payload

_fields = ["payload_type", "payload"]

def __init__(self, payload_type, payload):
self._payload_type = payload_type
self._payload = payload
Expand All @@ -30,7 +67,7 @@ def payload_type(self):
return self._payload_type


class GenericDoIPNegativeAcknowledge:
class GenericDoIPNegativeAcknowledge(DoIPMessage):
"""Generic header negative acknowledge structure. See Table 18"""

payload_type = 0x0000
Expand All @@ -51,6 +88,8 @@ def unpack(cls, payload_bytes, payload_length):
def pack(self):
return struct.pack("!B", self._nack_code)

_fields = ["nack_code"]

def __init__(self, nack_code):
self._nack_code = nack_code

Expand All @@ -65,11 +104,13 @@ def nack_code(self):
return self._nack_code


class AliveCheckRequest:
class AliveCheckRequest(DoIPMessage):
"""Alive check request - Table 27"""

payload_type = 0x0007

_fields = []

@classmethod
def unpack(cls, payload_bytes, payload_length):
return AliveCheckRequest()
Expand All @@ -78,7 +119,7 @@ def pack(self):
return bytearray()


class AliveCheckResponse:
class AliveCheckResponse(DoIPMessage):
"""Alive check resopnse - Table 28"""

payload_type = 0x0008
Expand All @@ -90,6 +131,8 @@ def unpack(cls, payload_bytes, payload_length):
def pack(self):
return struct.pack("!H", self._source_address)

_fields = ["source_address"]

def __init__(self, source_address):
self._source_address = source_address

Expand All @@ -113,11 +156,13 @@ def source_address(self):
return self._source_address


class DoipEntityStatusRequest:
class DoipEntityStatusRequest(DoIPMessage):
"""DoIP entity status request - Table 10"""

payload_type = 0x4001

_fields = []

@classmethod
def unpack(cls, payload_bytes, payload_length):
return DoipEntityStatusRequest()
Expand All @@ -126,11 +171,13 @@ def pack(self):
return bytearray()


class DiagnosticPowerModeRequest:
class DiagnosticPowerModeRequest(DoIPMessage):
"""Diagnostic power mode information request - Table 8"""

payload_type = 0x4003

_fields = []

@classmethod
def unpack(cls, payload_bytes, payload_length):
return DiagnosticPowerModeRequest()
Expand All @@ -139,11 +186,13 @@ def pack(self):
return bytearray()


class DiagnosticPowerModeResponse:
class DiagnosticPowerModeResponse(DoIPMessage):
"""Diagnostic power mode information response - Table 9"""

payload_type = 0x4004

_fields = ["diagnostic_power_mode"]

class DiagnosticPowerMode(IntEnum):
"""Diagnostic power mode - See Table 9"""

Expand Down Expand Up @@ -174,11 +223,13 @@ def diagnostic_power_mode(self):
)


class RoutingActivationRequest:
class RoutingActivationRequest(DoIPMessage):
"""Routing activation request. Table 46"""

payload_type = 0x0005

_fields = ["source_address", "activation_type", "reserved", "vm_specific"]

class ActivationType(IntEnum):
"""See Table 47 - Routing activation request activation types"""

Expand Down Expand Up @@ -253,11 +304,13 @@ def vm_specific(self):
return self._vm_specific


class VehicleIdentificationRequest:
class VehicleIdentificationRequest(DoIPMessage):
"""Vehicle identification request message. See Table 2"""

payload_type = 0x0001

_fields = []

@classmethod
def unpack(cls, payload_bytes, payload_length):
return VehicleIdentificationRequest()
Expand All @@ -266,11 +319,13 @@ def pack(self):
return bytearray()


class VehicleIdentificationRequestWithEID:
class VehicleIdentificationRequestWithEID(DoIPMessage):
"""Vehicle identification request message with EID. See Table 3"""

payload_type = 0x0002

_fields = ["eid"]

@classmethod
def unpack(cls, payload_bytes, payload_length):
return VehicleIdentificationRequestWithEID(
Expand All @@ -294,11 +349,13 @@ def eid(self):
return self._eid


class VehicleIdentificationRequestWithVIN:
class VehicleIdentificationRequestWithVIN(DoIPMessage):
"""Vehicle identification request message with VIN. See Table 4"""

payload_type = 0x0003

_fields = ["vin"]

@classmethod
def unpack(cls, payload_bytes, payload_length):
return VehicleIdentificationRequestWithVIN(
Expand All @@ -322,14 +379,25 @@ def vin(self):
Values: ASCII
"""
return self._vin.decode("ascii")
if type(self._vin) is bytes:
return self._vin.decode("ascii")
else:
return self._vin


class RoutingActivationResponse:
class RoutingActivationResponse(DoIPMessage):
"""Payload type routing activation response."""

payload_type = 0x0006

_fields = [
"client_logical_address",
"logical_address",
"response_code",
"reserved",
"vm_specific",
]

class ResponseCode(IntEnum):
"""See Table 48"""

Expand Down Expand Up @@ -440,7 +508,7 @@ def vm_specific(self):
return self._vm_specific


class DiagnosticMessage:
class DiagnosticMessage(DoIPMessage):
"""Diagnostic Message - see Table 21 "Payload type diagnostic message structure"
Description: Wrapper for diagnostic (UDS) payloads. The same message is used for
Expand All @@ -450,6 +518,8 @@ class DiagnosticMessage:

payload_type = 0x8001

_fields = ["source_address", "target_address", "user_data"]

@classmethod
def unpack(cls, payload_bytes, payload_length):
return DiagnosticMessage(
Expand Down Expand Up @@ -517,7 +587,7 @@ def user_data(self):
return self._user_data


class DiagnosticMessageNegativeAcknowledgement:
class DiagnosticMessageNegativeAcknowledgement(DoIPMessage):
"""A negative acknowledgement of the previously received diagnostic (UDS) message.
Indicates that the previously received diagnostic message was rejected. Reasons could
Expand All @@ -528,6 +598,8 @@ class DiagnosticMessageNegativeAcknowledgement:

payload_type = 0x8003

_fields = ["source_address", "target_address", "nack_code", "previous_message_data"]

class NackCodes(IntEnum):
"""Diagnostic message negative acknowledge codes (See Table 26)"""

Expand Down Expand Up @@ -615,7 +687,7 @@ def previous_message_data(self):
return None


class DiagnosticMessagePositiveAcknowledgement:
class DiagnosticMessagePositiveAcknowledgement(DoIPMessage):
"""A positive acknowledgement of the previously received diagnostic (UDS) message.
"...indicates a correctly received diagnostic message, which is processed and put into the transmission
Expand All @@ -626,6 +698,8 @@ class DiagnosticMessagePositiveAcknowledgement:

payload_type = 0x8002

_fields = ["source_address", "target_address", "ack_code", "previous_message_data"]

@classmethod
def unpack(cls, payload_bytes, payload_length):
return DiagnosticMessagePositiveAcknowledgement(
Expand Down Expand Up @@ -702,11 +776,18 @@ def previous_message_data(self):
return None


class EntityStatusResponse:
class EntityStatusResponse(DoIPMessage):
"""DoIP entity status response. Table 11"""

payload_type = 0x4002

_fields = [
"node_type",
"max_concurrent_sockets",
"currently_open_sockets",
"max_data_size",
]

@classmethod
def unpack(cls, payload_bytes, payload_length):
if payload_length == 3:
Expand Down Expand Up @@ -794,11 +875,20 @@ def max_data_size(self):
return self._max_data_size


class VehicleIdentificationResponse:
class VehicleIdentificationResponse(DoIPMessage):
"""Payload type vehicle announcement/identification response message Table 5"""

payload_type = 0x0004

_fields = [
"vin",
"logical_address",
"eid",
"gid",
"further_action_required",
"vin_sync_status",
]

class SynchronizationStatusCodes(IntEnum):
"""VIN/GID synchronization status code values (Table 7)
Expand Down Expand Up @@ -834,15 +924,15 @@ def unpack(cls, payload_bytes, payload_length):
)

def pack(self):
if self._vin_gid_sync_status is not None:
if self._vin_sync_status is not None:
return struct.pack(
"!17sH6s6sBB",
self._vin.encode("ascii"),
self._logical_address,
self._eid,
self._gid,
self._further_action_required,
self._vin_gid_sync_status,
self._vin_sync_status,
)
else:
return struct.pack(
Expand All @@ -861,14 +951,14 @@ def __init__(
eid,
gid,
further_action_required,
vin_gid_sync_staus=None,
vin_gid_sync_status=None,
):
self._vin = vin
self._logical_address = logical_address
self._eid = eid
self._gid = gid
self._further_action_required = further_action_required
self._vin_gid_sync_status = vin_gid_sync_staus
self._vin_sync_status = vin_gid_sync_status

@property
def vin(self):
Expand All @@ -880,7 +970,10 @@ def vin(self):
Values: ASCII
"""
return self._vin.decode("ascii")
if type(self._vin) is bytes:
return self._vin.decode("ascii")
else:
return self._vin

@property
def logical_address(self):
Expand Down Expand Up @@ -945,9 +1038,9 @@ def vin_sync_status(self):
Description: "This is the additional information to notify the client DoIP entity that all DoIP entities
have synchronized their information about the VIN or GID of the vehicle"
"""
if self._vin_gid_sync_status is not None:
if self._vin_sync_status is not None:
return VehicleIdentificationResponse.SynchronizationStatusCodes(
self._vin_gid_sync_status
self._vin_sync_status
)
else:
return None
Expand Down
Loading

0 comments on commit f26a6b3

Please sign in to comment.