Skip to content

Ethernet frames partial handling #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
introduce ethernet frame and unsupported msgs
  • Loading branch information
lorenzoditucci committed Apr 28, 2025
commit 7ceffd37c5b0141889153b89c723048a67e3889e
73 changes: 73 additions & 0 deletions can/ethernetframe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from enum import Enum
from typing import Optional
from . import typechecking

class DirectionFlag(Enum):
"""The state in which ethernetFrame dir can be"""

Rx = 0
Tx = 1
TxRq = 2

class EthernetFrame:
"""

"""

def __init__(
self,
timestamp: float = 0.0,
source_address: Optional[bytearray] = None,
channel: Optional[typechecking.Channel] = None,
destination_address: Optional[bytearray] = None,
direction: DirectionFlag = None,

# EtherType indicates the protocol for ethernet payload data
type: Optional[bytearray] = None,

# TPID when VLAN tag valid, zero when no
# VLAN. See Ethernet standard specification.
tpid: Optional[int] = 0,

# TCI when VLAND tag valid, zero when no
# VLAN. See Ethernet standard specification.
tci: Optional[int] = 0,

payloadLength: int = 0,
# TODO: check here typechecking.CanData
data: Optional[typechecking.CanData] = None

):
self.timestamp = timestamp
self.source_address = source_address
self.channel = channel
self.destination_address = destination_address
self.direction = direction
self.type = type
self.tpid = tpid
self.tci = tci
self.payloadLength = payloadLength

if data is None:
self.payload = bytearray()
elif isinstance(data, bytearray):
self.payload = data
else:
try:
self.payload = bytearray(data)
except TypeError as error:
err = f"Couldn't create message from {data} ({type(data)})"
raise TypeError(err) from error

def __str__(self) -> str:
field_strings = [f"ETHF - Timestamp: {self.timestamp:>15.6f}"]
field_strings.append(f"Source Address: {self.source_address}")
field_strings.append(f"Destination Address: {self.destination_address}")
field_strings.append(f"Direction: {self.direction}")
field_strings.append(f"EtherType: {self.type}")
field_strings.append(f"TPID: {self.tpid}")
field_strings.append(f"TCI: {self.tci}")
field_strings.append(f"Payload Length: {self.payloadLength}")
field_strings.append(f"Payload: {self.payload}")

return "\n".join(field_strings)
2 changes: 1 addition & 1 deletion can/ethernetframeext.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __init__( # pylint: disable=too-many-locals, too-many-arguments
raise TypeError(err) from error

def __str__(self) -> str:
field_strings = [f"(ETHERNET) Timestamp: {self.timestamp:>15.6f}"]
field_strings = [f"ETHFEXT - Timestamp: {self.timestamp:>15.6f}"]
# if self.is_extended_id:
# arbitration_id_string = f"{self.arbitration_id:08x}"
# else:
Expand Down
87 changes: 85 additions & 2 deletions can/io/blf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import zlib
from typing import Any, BinaryIO, Generator, List, Optional, Tuple, Union, cast

from can.ethernetframe import EthernetFrame
from can.unsupportedmessage import UnsupportedMessage

from ..message import Message
from ..ethernetframeext import EthernetFrameExt
from ..typechecking import StringPathLike
Expand Down Expand Up @@ -77,6 +80,9 @@ class BLFParseError(Exception):
# group name length, marker name length, description length
GLOBAL_MARKER_STRUCT = struct.Struct("<LLL3xBLLL12x")

# sourceAddress, channel, destinationAddress, dir, type, tpid, tci, payloadLength, reservedEthernetFrame, payload
ETHERNET_FRAME_STRUCT = struct.Struct("<6BH6BHHHHHQ")

# structLength, flags, channel, hardwareChannel, frameduration, framechecksum, dir, frameLength, framehandle, reservedEthernetFrameEx, frameData
ETHERNET_FRAME_EX_STRUCT = struct.Struct("<HHHHQLHHLL")

Expand Down Expand Up @@ -123,7 +129,7 @@ class BLFParseError(Exception):


def timestamp_to_systemtime(timestamp: float) -> TSystemTime:
if timestamp is None or timestamp < 631152000:
if timestamp is None:
# Probably not a Unix timestamp
return 0, 0, 0, 0, 0, 0, 0, 0
t = datetime.datetime.fromtimestamp(round(timestamp, 3))
Expand Down Expand Up @@ -250,6 +256,8 @@ def _parse_data(self, data, encountered_ids):
unpack_can_fd_64_msg = CAN_FD_MSG_64_STRUCT.unpack_from
can_fd_64_msg_size = CAN_FD_MSG_64_STRUCT.size
unpack_can_error_ext = CAN_ERROR_EXT_STRUCT.unpack_from
unpack_ethernet_frame = ETHERNET_FRAME_STRUCT.unpack_from
ethernet_frame_msg_size = ETHERNET_FRAME_STRUCT.size
unpack_ethernet_frame_ex = ETHERNET_FRAME_EX_STRUCT.unpack_from
ethernet_ext_msg_size = ETHERNET_FRAME_EX_STRUCT.size

Expand Down Expand Up @@ -383,8 +391,36 @@ def _parse_data(self, data, encountered_ids):
channel=channel - 1,
)
elif obj_type == ETHERNET_FRAME:
print("ETHERNET_FRAME")
print("ETHF")
members = unpack_ethernet_frame(data, pos)
(
sourceAddress,
channel,
destinationAddress,
direction,
frameType,
tpid,
tci,
payloadLength,
_
) = members

pos += ethernet_frame_msg_size
yield EthernetFrame(
timestamp=timestamp,
source_address=sourceAddress,
channel=channel,
destination_address=destinationAddress,
direction=direction,
type=frameType,
tpid=tpid,
tci=tci,
payloadLength=payloadLength,
data=data[pos : pos + payloadLength],
)

elif obj_type == ETHERNET_FRAME_EX:
print("ETHFEXT")
members = unpack_ethernet_frame_ex(data, pos)
(
structLength,
Expand Down Expand Up @@ -414,6 +450,12 @@ def _parse_data(self, data, encountered_ids):

else:
encountered_ids.add(obj_type)
print("Unsupported message type: ", obj_type)
yield UnsupportedMessage(
objectType=obj_type,
timestamp=timestamp,
data=data
)

pos = next_pos

Expand Down Expand Up @@ -506,13 +548,54 @@ def _write_header(self, filesize):
self.file.write(b"\x00" * (FILE_HEADER_SIZE - FILE_HEADER_STRUCT.size))

def on_message_received(self, msg):

if isinstance(msg, UnsupportedMessage):
data = msg.data
self._add_object(msg.objectType, data, msg.timestamp)
return


channel = channel2int(msg.channel)
if channel is None:
channel = self.channel
else:
# Many interfaces start channel numbering at 0 which is invalid
channel += 1


if isinstance(msg, EthernetFrameExt):
# TODO: double check here
hardware_channel = channel2int(msg.hardware_channel)
data = ETHERNET_FRAME_EX_STRUCT.pack(
msg.struct_length,
msg.flags,
channel,
hardware_channel if hardware_channel is not None else 0,
msg.duration if msg.duration is not None else 0,
msg.checksum if msg.checksum is not None else 0,
msg.direction if msg.direction is not None else 0,
len(msg.data),
msg.handle,
0, # reservedEthernetFrameEx
)
self._add_object(ETHERNET_FRAME_EX, data + msg.data, msg.timestamp)
return

if isinstance(msg, EthernetFrame):
data = ETHERNET_FRAME_STRUCT.pack(
msg.source_address,
channel,
msg.destination_address,
msg.direction,
msg.type,
msg.tpid,
msg.tci,
len(msg.payload),
0, # reservedEthernetFrame
)
self._add_object(ETHERNET_FRAME, data + msg.payload, msg.timestamp)
return

arb_id = msg.arbitration_id
if msg.is_extended_id:
arb_id |= CAN_MSG_EXT
Expand Down
34 changes: 34 additions & 0 deletions can/unsupportedmessage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import Optional
from . import typechecking

class UnsupportedMessage:
"""
Class to handle all unsupported messages.
"""

def __init__(
self,
objectType: int = 0,
timestamp: float = 0.0,
data: Optional[typechecking.CanData] = None
):
self.objectType = objectType
self.timestamp = timestamp
self.data = data

if data is None:
self.data = bytearray()
elif isinstance(data, bytearray):
self.data = data
else:
try:
self.data = bytearray(data)
except TypeError as error:
err = f"Couldn't create message from {data} ({type(data)})"
raise TypeError(err) from error
def __str__(self) -> str:
field_strings = [f"UnsupportedMessage - Timestamp: {self.timestamp:>15.6f}"]
field_strings.append(f"Data: {self.data}")
return "\n".join(field_strings)