Skip to content

Commit a137030

Browse files
Ethernet frames partial handling (#1)
1 parent 654a02a commit a137030

File tree

4 files changed

+323
-4
lines changed

4 files changed

+323
-4
lines changed

can/ethernetframe.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from enum import Enum
2+
from typing import Optional
3+
from . import typechecking
4+
5+
class DirectionFlag(Enum):
6+
"""The state in which ethernetFrame dir can be"""
7+
8+
Rx = 0
9+
Tx = 1
10+
TxRq = 2
11+
12+
class EthernetFrame:
13+
14+
def __init__(
15+
self,
16+
timestamp: float = 0.0,
17+
source_address: Optional[bytearray] = None,
18+
channel: Optional[typechecking.Channel] = None,
19+
destination_address: Optional[bytearray] = None,
20+
direction: Optional[DirectionFlag] = None,
21+
22+
# EtherType indicates the protocol for ethernet payload data
23+
ether_type: Optional[bytearray] = None,
24+
25+
# TPID when VLAN tag valid, zero when no
26+
# VLAN. See Ethernet standard specification.
27+
tpid: Optional[int] = None,
28+
29+
# TCI when VLAND tag valid, zero when no
30+
# VLAN. See Ethernet standard specification.
31+
tci: Optional[int] = None,
32+
payloadLength: int = 0,
33+
data: Optional[typechecking.CanData] = None,
34+
):
35+
self.timestamp = timestamp
36+
self.source_address = source_address
37+
self.channel = channel
38+
self.destination_address = destination_address
39+
self.direction = direction
40+
self.type = ether_type
41+
self.tpid = tpid
42+
self.tci = tci
43+
self.payloadLength = payloadLength
44+
45+
if data is None:
46+
self.data = bytearray()
47+
elif isinstance(data, bytearray):
48+
self.data = data
49+
else:
50+
try:
51+
self.data = bytearray(data)
52+
except TypeError as error:
53+
err = f"Couldn't create message from {data} ({type(data)})"
54+
raise TypeError(err) from error
55+
56+
def __str__(self) -> str:
57+
field_strings = [f"EthernetFrame - Timestamp: {self.timestamp:>15.6f}"]
58+
field_strings.append(f"Source Address: {self.source_address}")
59+
field_strings.append(f"Destination Address: {self.destination_address}")
60+
field_strings.append(f"Direction: {self.direction}")
61+
field_strings.append(f"EtherType: {self.type}")
62+
field_strings.append(f"TPID: {self.tpid}")
63+
field_strings.append(f"TCI: {self.tci}")
64+
field_strings.append(f"Payload Length: {self.payloadLength}")
65+
field_strings.append(f"Data: {self.data}")
66+
67+
return "\n".join(field_strings)

can/ethernetframeext.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from typing import Optional
2+
3+
from .ethernetframe import DirectionFlag
4+
from . import typechecking
5+
6+
class EthernetFrameExt: # pylint: disable=too-many-instance-attributes; OK for a dataclass
7+
8+
def __init__( # pylint: disable=too-many-locals, too-many-arguments
9+
self,
10+
timestamp: float = 0.0,
11+
struct_length: int = 0,
12+
flags: int = 0,
13+
channel: Optional[typechecking.Channel] = None,
14+
hardware_channel: Optional[int] = None,
15+
duration: Optional[int] = None,
16+
checksum: Optional[int] = None,
17+
direction: Optional[DirectionFlag] = None,
18+
# Number of valid frameData bytes
19+
length: int = 0,
20+
# Handle which refer the corresponding EthernetFrameForwarded event
21+
handle: int = 0,
22+
# Frame data
23+
data: Optional[typechecking.CanData] = None
24+
):
25+
self.timestamp = timestamp
26+
self.struct_length = struct_length
27+
self.flags = flags
28+
self.channel = channel
29+
self.hardware_channel = hardware_channel
30+
self.duration = duration
31+
self.checksum = checksum
32+
self.direction= direction
33+
self.length = length
34+
self.handle = handle
35+
36+
if data is None:
37+
self.data = bytearray()
38+
elif isinstance(data, bytearray):
39+
self.data = data
40+
else:
41+
try:
42+
self.data = bytearray(data)
43+
except TypeError as error:
44+
err = f"Couldn't create message from {data} ({type(data)})"
45+
raise TypeError(err) from error
46+
47+
def __str__(self) -> str:
48+
field_strings = [f"ETHFEXT - Timestamp: {self.timestamp:>15.6f}"]
49+
flag_string = f"flags: {self.flags}"
50+
field_strings.append(flag_string)
51+
52+
53+
data_strings = "Data: "
54+
if self.data is not None:
55+
data_strings += self.data[: len(self.data)].hex(" ")
56+
if data_strings: # if not empty
57+
field_strings.append(data_strings.ljust(24, " "))
58+
else:
59+
field_strings.append(" " * 24)
60+
61+
if (self.data is not None) and (self.data.isalnum()):
62+
field_strings.append(f"'{self.data.decode('utf-8', 'replace')}'")
63+
64+
if self.channel is not None:
65+
try:
66+
field_strings.append(f"Channel: {self.channel}")
67+
except UnicodeEncodeError:
68+
pass
69+
if self.hardware_channel is not None:
70+
try:
71+
field_strings.append(f"Hardware Channel: {self.hardware_channel}")
72+
except UnicodeEncodeError:
73+
pass
74+
if self.direction is not None:
75+
try:
76+
field_strings.append(f"Direction: {self.direction}")
77+
except UnicodeEncodeError:
78+
pass
79+
if self.length is not None:
80+
try:
81+
field_strings.append(f"Length: {self.length}")
82+
except UnicodeEncodeError:
83+
pass
84+
if self.handle is not None:
85+
try:
86+
field_strings.append(f"Handle: {self.handle}")
87+
except UnicodeEncodeError:
88+
pass
89+
if self.checksum is not None:
90+
try:
91+
field_strings.append(f"Checksum: {self.checksum}")
92+
except UnicodeEncodeError:
93+
pass
94+
if self.duration is not None:
95+
try:
96+
field_strings.append(f"Duration: {self.duration}")
97+
except UnicodeEncodeError:
98+
pass
99+
if self.struct_length is not None:
100+
try:
101+
field_strings.append(f"Struct Length: {self.struct_length}")
102+
except UnicodeEncodeError:
103+
pass
104+
105+
106+
107+
return "\n".join(field_strings).strip()

can/io/blf.py

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
from typing import Any, BinaryIO, Generator, List, Optional, Tuple, Union, cast
2121

2222
from ..message import Message
23+
from ..ethernetframe import EthernetFrame
24+
from ..unknownmessage import UnknownMessage
25+
from ..ethernetframeext import EthernetFrameExt
2326
from ..typechecking import StringPathLike
2427
from ..util import channel2int, dlc2len, len2dlc
2528
from .generic import BinaryIOMessageReader, FileIOMessageWriter
@@ -76,14 +79,27 @@ class BLFParseError(Exception):
7679
# group name length, marker name length, description length
7780
GLOBAL_MARKER_STRUCT = struct.Struct("<LLL3xBLLL12x")
7881

82+
# sourceAddress, channel, destinationAddress, dir, type, tpid, tci, payloadLength, reservedEthernetFrame
83+
ETHERNET_FRAME_STRUCT = struct.Struct("<6sH6sHHHHHQ")
84+
# after this, max 1500 data bytes of payload follows (ethernet header not included)
85+
86+
# structLength, flags, channel, hardwareChannel, frameduration, framechecksum, dir, frameLength, framehandle, reservedEthernetFrameEx
87+
ETHERNET_FRAME_EX_STRUCT = struct.Struct("<HHHHQLHHLL")
88+
# after this follows up to 1612 bytes of frameData containing: Ethernet header + Ethernet payload
7989

8090
CAN_MESSAGE = 1
8191
LOG_CONTAINER = 10
92+
ETHERNET_FRAME = 71
8293
CAN_ERROR_EXT = 73
8394
CAN_MESSAGE2 = 86
8495
GLOBAL_MARKER = 96
8596
CAN_FD_MESSAGE = 100
8697
CAN_FD_MESSAGE_64 = 101
98+
ETHERNET_FRAME_EX = 120 # < Ethernet packet extended object
99+
# Related ethernet containers not yet supported:
100+
# ETHERNET_FRAME_FORWARDED = 121 # < Ethernet packet forwarded object
101+
# ETHERNET_ERROR_EX = 122 # < Ethernet error extended object
102+
# ETHERNET_ERROR_FORWARDED = 123 # < Ethernet error forwarded object
87103

88104
NO_COMPRESSION = 0
89105
ZLIB_DEFLATE = 2
@@ -100,7 +116,7 @@ class BLFParseError(Exception):
100116

101117

102118
def timestamp_to_systemtime(timestamp: float) -> TSystemTime:
103-
if timestamp is None or timestamp < 631152000:
119+
if timestamp is None:
104120
# Probably not a Unix timestamp
105121
return 0, 0, 0, 0, 0, 0, 0, 0
106122
t = datetime.datetime.fromtimestamp(round(timestamp, 3))
@@ -167,7 +183,7 @@ def __init__(
167183
self._tail = b""
168184
self._pos = 0
169185

170-
def __iter__(self) -> Generator[Message, None, None]:
186+
def __iter__(self) -> Generator[Union[Message, UnknownMessage, EthernetFrame, EthernetFrameExt], None, None]:
171187
while True:
172188
data = self.file.read(OBJ_HEADER_BASE_STRUCT.size)
173189
if not data:
@@ -196,7 +212,7 @@ def __iter__(self) -> Generator[Message, None, None]:
196212
yield from self._parse_container(data)
197213
self.stop()
198214

199-
def _parse_container(self, data):
215+
def _parse_container(self, data: bytes) -> Generator[Union[Message, UnknownMessage, EthernetFrame, EthernetFrameExt], None, None]:
200216
if self._tail:
201217
data = b"".join((self._tail, data))
202218
try:
@@ -207,7 +223,7 @@ def _parse_container(self, data):
207223
# Save the remaining data that could not be processed
208224
self._tail = data[self._pos :]
209225

210-
def _parse_data(self, data):
226+
def _parse_data(self, data: bytes) -> Generator[Union[Message, UnknownMessage, EthernetFrame, EthernetFrameExt], None, None]:
211227
"""Optimized inner loop by making local copies of global variables
212228
and class members and hardcoding some values."""
213229
unpack_obj_header_base = OBJ_HEADER_BASE_STRUCT.unpack_from
@@ -221,6 +237,10 @@ def _parse_data(self, data):
221237
unpack_can_fd_64_msg = CAN_FD_MSG_64_STRUCT.unpack_from
222238
can_fd_64_msg_size = CAN_FD_MSG_64_STRUCT.size
223239
unpack_can_error_ext = CAN_ERROR_EXT_STRUCT.unpack_from
240+
unpack_ethernet_frame = ETHERNET_FRAME_STRUCT.unpack_from
241+
ethernet_frame_msg_size = ETHERNET_FRAME_STRUCT.size
242+
unpack_ethernet_frame_ex = ETHERNET_FRAME_EX_STRUCT.unpack_from
243+
ethernet_ext_msg_size = ETHERNET_FRAME_EX_STRUCT.size
224244

225245
start_timestamp = self.start_timestamp
226246
max_pos = len(data)
@@ -351,6 +371,70 @@ def _parse_data(self, data):
351371
data=data[pos : pos + valid_bytes],
352372
channel=channel - 1,
353373
)
374+
elif obj_type == ETHERNET_FRAME:
375+
members = unpack_ethernet_frame(data, pos)
376+
(
377+
sourceAddress,
378+
channel,
379+
destinationAddress,
380+
direction,
381+
frameType,
382+
tpid,
383+
tci,
384+
payloadLength,
385+
_
386+
) = members
387+
388+
pos += ethernet_frame_msg_size
389+
yield EthernetFrame(
390+
timestamp=timestamp,
391+
source_address=sourceAddress,
392+
channel=channel,
393+
destination_address=destinationAddress,
394+
direction=direction,
395+
ether_type=frameType,
396+
tpid=tpid,
397+
tci=tci,
398+
payloadLength=payloadLength,
399+
data=data[pos : pos + payloadLength]
400+
)
401+
402+
elif obj_type == ETHERNET_FRAME_EX:
403+
members = unpack_ethernet_frame_ex(data, pos)
404+
(
405+
structLength,
406+
flags,
407+
channel,
408+
hardwareChannel,
409+
frameDuration,
410+
frameChecksum,
411+
direction,
412+
frameLength,
413+
frameHandle,
414+
_
415+
) = members
416+
417+
pos += ethernet_ext_msg_size
418+
yield EthernetFrameExt(
419+
timestamp=timestamp,
420+
struct_length=structLength,
421+
flags=flags,
422+
channel=channel,
423+
hardware_channel=hardwareChannel,
424+
duration=frameDuration,
425+
checksum=frameChecksum,
426+
handle=frameHandle,
427+
length=frameLength,
428+
direction=direction,
429+
data=data[pos : pos + frameLength]
430+
)
431+
432+
else:
433+
yield UnknownMessage(
434+
objectType=obj_type,
435+
timestamp=timestamp,
436+
data=data[pos : next_pos]
437+
)
354438

355439
pos = next_pos
356440

@@ -443,13 +527,50 @@ def _write_header(self, filesize):
443527
self.file.write(b"\x00" * (FILE_HEADER_SIZE - FILE_HEADER_STRUCT.size))
444528

445529
def on_message_received(self, msg):
530+
531+
if isinstance(msg, UnknownMessage):
532+
data = msg.data
533+
self._add_object(msg.objectType, data, msg.timestamp)
534+
return
535+
446536
channel = channel2int(msg.channel)
447537
if channel is None:
448538
channel = self.channel
449539
else:
450540
# Many interfaces start channel numbering at 0 which is invalid
451541
channel += 1
452542

543+
if isinstance(msg, EthernetFrameExt):
544+
data = ETHERNET_FRAME_EX_STRUCT.pack(
545+
msg.struct_length,
546+
msg.flags,
547+
channel,
548+
msg.hardware_channel if msg.hardware_channel is not None else 0,
549+
msg.duration if msg.duration is not None else 0,
550+
msg.checksum if msg.checksum is not None else 0,
551+
msg.direction if msg.direction is not None else 0,
552+
len(msg.data),
553+
msg.handle,
554+
0, # reservedEthernetFrameEx
555+
)
556+
self._add_object(ETHERNET_FRAME_EX, data + msg.data, msg.timestamp)
557+
return
558+
559+
if isinstance(msg, EthernetFrame):
560+
data = ETHERNET_FRAME_STRUCT.pack(
561+
msg.source_address,
562+
channel,
563+
msg.destination_address,
564+
msg.direction,
565+
msg.type,
566+
msg.tpid,
567+
msg.tci,
568+
len(msg.data),
569+
0, # reservedEthernetFrame
570+
)
571+
self._add_object(ETHERNET_FRAME, data + msg.data, msg.timestamp)
572+
return
573+
453574
arb_id = msg.arbitration_id
454575
if msg.is_extended_id:
455576
arb_id |= CAN_MSG_EXT

0 commit comments

Comments
 (0)