Ejemplo n.º 1
0
async def _unittest_can_pythoncan_socketcan() -> None:
    asyncio.get_running_loop().slow_callback_duration = 5.0

    media_a = PythonCANMedia("socketcan:vcan2", 0, 8)
    media_b = PythonCANMedia("socketcan:vcan2", 0, 64)

    rx_a: typing.List[typing.Tuple[Timestamp, Envelope]] = []
    rx_b: typing.List[typing.Tuple[Timestamp, Envelope]] = []

    def on_rx_a(frames: typing.Iterable[typing.Tuple[Timestamp, Envelope]]) -> None:
        nonlocal rx_a
        rx_a += list(frames)

    def on_rx_b(frames: typing.Iterable[typing.Tuple[Timestamp, Envelope]]) -> None:
        nonlocal rx_b
        rx_b += list(frames)

    media_a.start(on_rx_a, no_automatic_retransmission=False)
    media_b.start(on_rx_b, no_automatic_retransmission=False)

    ts_begin = Timestamp.now()
    await media_a.send(
        [
            Envelope(DataFrame(FrameFormat.EXTENDED, 0xBADC0FE, bytearray(b"123")), loopback=True),
            Envelope(DataFrame(FrameFormat.EXTENDED, 0x12345678, bytearray(b"456")), loopback=False),
        ],
        asyncio.get_event_loop().time() + 1.0,
    )
    await asyncio.sleep(1.0)
    ts_end = Timestamp.now()

    assert len(rx_b) == 2
    assert ts_begin.monotonic_ns <= rx_b[0][0].monotonic_ns <= ts_end.monotonic_ns
    assert ts_begin.monotonic_ns <= rx_b[1][0].monotonic_ns <= ts_end.monotonic_ns
    assert ts_begin.system_ns <= rx_b[0][0].system_ns <= ts_end.system_ns
    assert ts_begin.system_ns <= rx_b[1][0].system_ns <= ts_end.system_ns
    assert not rx_b[0][1].loopback
    assert not rx_b[1][1].loopback
    assert rx_b[0][1].frame.identifier == 0xBADC0FE
    assert rx_b[1][1].frame.identifier == 0x12345678
    assert rx_b[0][1].frame.data == b"123"
    assert rx_b[1][1].frame.data == b"456"

    assert len(rx_a) == 1
    assert ts_begin.monotonic_ns <= rx_a[0][0].monotonic_ns <= ts_end.monotonic_ns
    assert ts_begin.system_ns <= rx_a[0][0].system_ns <= ts_end.system_ns
    assert rx_a[0][1].loopback
    assert rx_a[0][1].frame.identifier == 0xBADC0FE
    assert rx_a[0][1].frame.data == b"123"

    media_a.close()
    media_b.close()
    media_a.close()  # Ensure idempotency.
    media_b.close()
def _unittest_issue_198() -> None:
    source_node_id = 88
    transfer_id_timeout_ns = 900

    def mk_frame(
        padded_payload: bytes | str,
        transfer_id: int,
        start_of_transfer: bool,
        end_of_transfer: bool,
        toggle_bit: bool,
    ) -> UAVCANFrame:
        return UAVCANFrame(
            identifier=0xBADC0FE,
            padded_payload=memoryview(padded_payload if isinstance(
                padded_payload, bytes) else padded_payload.encode()),
            transfer_id=transfer_id,
            start_of_transfer=start_of_transfer,
            end_of_transfer=end_of_transfer,
            toggle_bit=toggle_bit,
        )

    rx = TransferReassembler(source_node_id, 50)

    # First, ensure that the reassembler is initialized, by feeding it a valid transfer at least once.
    assert rx.process_frame(
        timestamp=Timestamp(system_ns=0, monotonic_ns=1000),
        priority=pyuavcan.transport.Priority.SLOW,
        frame=mk_frame("123", 0, True, True, True),
        transfer_id_timeout_ns=transfer_id_timeout_ns,
    ) == TransferFrom(
        timestamp=Timestamp(system_ns=0, monotonic_ns=1000),
        priority=pyuavcan.transport.Priority.SLOW,
        transfer_id=0,
        fragmented_payload=[
            memoryview(x if isinstance(x, (bytes, memoryview)) else x.encode())
            for x in ["123"]
        ],
        source_node_id=source_node_id,
    )

    # Next, feed the last frame of another transfer whose TID/TOG match the expected state of the reassembler.
    # This should be recognized as a CRC error.
    assert (rx.process_frame(
        timestamp=Timestamp(system_ns=0, monotonic_ns=1000),
        priority=pyuavcan.transport.Priority.SLOW,
        frame=mk_frame("456", 1, False, True, True),
        transfer_id_timeout_ns=transfer_id_timeout_ns,
    ) == TransferReassemblyErrorID.TRANSFER_CRC_MISMATCH)
Ejemplo n.º 3
0
    async def _emit(self, header_payload_pairs: typing.Sequence[typing.Tuple[
        memoryview, memoryview]],
                    monotonic_deadline: float) -> typing.Optional[Timestamp]:
        """
        Returns the transmission timestamp of the first frame (which is the transfer timestamp) on success.
        Returns None if at least one frame could not be transmitted.
        """
        ts: typing.Optional[Timestamp] = None
        loop = asyncio.get_running_loop()
        for index, (header, payload) in enumerate(header_payload_pairs):
            try:
                # TODO: concatenation is inefficient. Use vectorized IO via sendmsg() instead!
                await asyncio.wait_for(
                    loop.sock_sendall(self._sock, b"".join((header, payload))),
                    timeout=monotonic_deadline - loop.time(),
                )

                # TODO: use socket timestamping when running on Linux (Windows does not support timestamping).
                # Depending on the chosen approach, timestamping on Linux may require us to launch a new thread
                # reading from the socket's error message queue and then matching the returned frames with a
                # pending loopback registry, kind of like it's done with CAN.
                ts = ts or Timestamp.now()

            except (asyncio.TimeoutError, asyncio.CancelledError):
                self._statistics.drops += len(header_payload_pairs) - index
                return None
            except Exception as ex:
                if _IGNORE_OS_ERROR_ON_SEND and isinstance(
                        ex, OSError) and self._sock.fileno() >= 0:
                    # Windows compatibility workaround -- if there are no registered multicast receivers on the
                    # loopback interface, send() may raise WinError 1231 or 10051. This error shall be suppressed.
                    _logger.debug(
                        "%r: Socket send error ignored (the likely cause is that there are no known receivers "
                        "on the other end of the link): %r",
                        self,
                        ex,
                    )
                    # To suppress the error properly, we have to pretend that the data was actually transmitted,
                    # so we populate the timestamp with a phony value anyway.
                    ts = ts or Timestamp.now()
                else:
                    self._statistics.errors += 1
                    raise

            self._statistics.frames += 1
            self._statistics.payload_bytes += len(payload)

        return ts
Ejemplo n.º 4
0
def _unittest_validate_and_finalize_transfer() -> None:
    ts = Timestamp.now()
    prio = Priority.FAST
    tid = 888888888
    src_nid = 1234

    def mk_transfer(fp: typing.Sequence[bytes]) -> TransferFrom:
        return TransferFrom(
            timestamp=ts,
            priority=prio,
            transfer_id=tid,
            fragmented_payload=list(map(memoryview, fp)),
            source_node_id=src_nid,
        )

    def call(fp: typing.Sequence[bytes]) -> typing.Optional[TransferFrom]:
        return _validate_and_finalize_transfer(
            timestamp=ts,
            priority=prio,
            transfer_id=tid,
            frame_payloads=list(map(memoryview, fp)),
            source_node_id=src_nid,
        )

    assert call([b""]) == mk_transfer([b""])
    assert call([b"hello world"]) == mk_transfer([b"hello world"])
    assert call([
        b"hello world", b"0123456789",
        TransferCRC.new(b"hello world", b"0123456789").value_as_bytes
    ]) == mk_transfer([b"hello world", b"0123456789"])
    assert call([b"hello world", b"0123456789"]) is None  # no CRC
Ejemplo n.º 5
0
    def __init__(
        self,
        source_node_id: int,
        extent_bytes: int,
        on_error_callback: typing.Callable[[TransferReassembler.Error], None],
    ):
        """
        :param source_node_id: The remote node-ID whose transfers this instance will be listening for.
            Anonymous transfers cannot be multi-frame transfers, so they are to be accepted as-is without any
            reassembly activities.

        :param extent_bytes: The maximum number of payload bytes per transfer.
            Payload that exceeds this size limit may be implicitly truncated (in the Specification this behavior
            is described as "implicit truncation rule").
            This value can be derived from the corresponding DSDL definition.

        :param on_error_callback: The callback is invoked whenever an error is detected.
            This is intended for diagnostic purposes only; the error information is not actionable.
            The error is logged by the caller at the DEBUG verbosity level together with reassembly context info.
        """
        # Constant configuration.
        self._source_node_id = int(source_node_id)
        self._extent_bytes = int(extent_bytes)
        self._on_error_callback = on_error_callback
        if self._source_node_id < 0 or self._extent_bytes < 0 or not callable(
                self._on_error_callback):
            raise ValueError("Invalid parameters")

        # Internal state.
        self._payloads: typing.List[memoryview] = [
        ]  # Payload fragments from the received frames.
        self._max_index: typing.Optional[
            int] = None  # Max frame index in transfer, None if unknown.
        self._timestamp = Timestamp(0, 0)  # First frame timestamp.
        self._transfer_id = 0  # Transfer-ID of the current transfer.
Ejemplo n.º 6
0
def _unittest_transfer_reassembler_anonymous() -> None:
    ts = Timestamp.now()
    prio = Priority.LOW
    assert TransferReassembler.construct_anonymous_transfer(
        ts,
        Frame(priority=prio,
              transfer_id=123456,
              index=0,
              end_of_transfer=True,
              payload=memoryview(b"abcdef")),
    ) == TransferFrom(timestamp=ts,
                      priority=prio,
                      transfer_id=123456,
                      fragmented_payload=[memoryview(b"abcdef")],
                      source_node_id=None)

    assert (TransferReassembler.construct_anonymous_transfer(
        ts,
        Frame(priority=prio,
              transfer_id=123456,
              index=1,
              end_of_transfer=True,
              payload=memoryview(b"abcdef")),
    ) is None)

    assert (TransferReassembler.construct_anonymous_transfer(
        ts,
        Frame(priority=prio,
              transfer_id=123456,
              index=0,
              end_of_transfer=False,
              payload=memoryview(b"abcdef")),
    ) is None)
Ejemplo n.º 7
0
def _unittest_frame_compile_service() -> None:
    from pyuavcan.transport import Priority, ServiceDataSpecifier, Timestamp

    f = SerialFrame(timestamp=Timestamp.now(),
                    priority=Priority.FAST,
                    source_node_id=SerialFrame.FRAME_DELIMITER_BYTE,
                    destination_node_id=None,
                    data_specifier=ServiceDataSpecifier(123, ServiceDataSpecifier.Role.RESPONSE),
                    transfer_id=1234567890123456789,
                    index=1234567,
                    end_of_transfer=False,
                    payload=memoryview(b''))

    buffer = bytearray(0 for _ in range(50))
    mv = f.compile_into(buffer)

    assert mv[0] == mv[-1] == SerialFrame.FRAME_DELIMITER_BYTE
    segment_cobs = bytes(mv[1:-1])
    assert SerialFrame.FRAME_DELIMITER_BYTE not in segment_cobs

    segment = cobs.decode(segment_cobs)

    # Header validation
    assert segment[0] == _VERSION
    assert segment[1] == int(Priority.FAST)
    assert (segment[2], segment[3]) == (SerialFrame.FRAME_DELIMITER_BYTE, 0)
    assert (segment[4], segment[5]) == (0xFF, 0xFF)
    assert segment[6:8] == ((1 << 15) | (1 << 14) | 123).to_bytes(2, 'little')
    assert segment[8:16] == b'\x00' * 8
    assert segment[16:24] == 1234567890123456789 .to_bytes(8, 'little')
    assert segment[24:28] == 1234567 .to_bytes(4, 'little')
    # Header CRC here

    # CRC validation
    assert segment[32:] == pyuavcan.transport.commons.crc.CRC32C.new(f.payload).value_as_bytes
Ejemplo n.º 8
0
 def callback(lls: LinkLayerCapture) -> None:
     nonlocal ts_last
     now = Timestamp.now()
     assert ts_last.monotonic_ns <= lls.timestamp.monotonic_ns <= now.monotonic_ns
     assert ts_last.system_ns <= lls.timestamp.system_ns <= now.system_ns
     ts_last = lls.timestamp
     sniffs.append(lls.packet)
Ejemplo n.º 9
0
def _unittest_transfer_reassembler_anonymous() -> None:
    from pyuavcan.transport import Timestamp, Priority, TransferFrom

    ts = Timestamp.now()
    prio = Priority.LOW
    assert TransferReassembler.construct_anonymous_transfer(
        Frame(timestamp=ts,
              priority=prio,
              transfer_id=123456,
              index=0,
              end_of_transfer=True,
              payload=memoryview(b'abcdef'))) == TransferFrom(
                  timestamp=ts,
                  priority=prio,
                  transfer_id=123456,
                  fragmented_payload=[memoryview(b'abcdef')],
                  source_node_id=None)

    assert TransferReassembler.construct_anonymous_transfer(
        Frame(timestamp=ts,
              priority=prio,
              transfer_id=123456,
              index=1,
              end_of_transfer=True,
              payload=memoryview(b'abcdef'))) is None

    assert TransferReassembler.construct_anonymous_transfer(
        Frame(timestamp=ts,
              priority=prio,
              transfer_id=123456,
              index=0,
              end_of_transfer=False,
              payload=memoryview(b'abcdef'))) is None
Ejemplo n.º 10
0
 def proxy(_: object, header: ctypes.Structure, packet: typing.Any) -> None:
     # Parse the header, extract the timestamp and the packet length.
     header = header.contents
     ts_ns = (header.ts.tv_sec * 1_000_000 + header.ts.tv_usec) * 1000
     ts = Timestamp(system_ns=ts_ns, monotonic_ns=time.monotonic_ns())
     length, real_length = header.caplen, header.len
     _logger.debug("%r: CAPTURED PACKET ts=%s dev=%r len=%d bytes", self, ts, name, length)
     if real_length != length:
         # In theory, this should never occur because we use a huge capture buffer.
         # On Windows, however, when using Npcap v0.96, the captured length is (always?) reported to be
         # 32 bytes shorter than the real length, despite the fact that the packet is not truncated.
         _logger.debug(
             "%r: Length mismatch in a packet captured from %r: real %r bytes, captured %r bytes",
             self,
             name,
             real_length,
             length,
         )
     # Create a copy of the payload. This is required per the libpcap API contract -- it says that the
     # memory is invalidated upon return from the callback.
     packet = memoryview(ctypes.cast(packet, ctypes.POINTER(ctypes.c_ubyte * length))[0]).tobytes()
     llp = decoder(memoryview(packet))
     if llp is None:
         if _logger.isEnabledFor(logging.INFO):
             _logger.info(
                 "%r: Link-layer packet of %d bytes captured from %r at %s could not be parsed. "
                 "The header is: %s",
                 self,
                 len(packet),
                 name,
                 ts,
                 packet[:32].hex(),
             )
     else:
         self._callback(LinkLayerCapture(timestamp=ts, packet=llp, device_name=name))
Ejemplo n.º 11
0
 async def send(self, frames: typing.Iterable[Envelope],
                monotonic_deadline: float) -> int:
     num_sent = 0
     loopback: typing.List[typing.Tuple[Timestamp, Envelope]] = []
     for f in frames:
         if self._closed:
             raise ResourceClosedError(repr(self))
         message = can.Message(
             arbitration_id=f.frame.identifier,
             is_extended_id=(f.frame.format == FrameFormat.EXTENDED),
             data=f.frame.data,
             is_fd=self._is_fd,
         )
         try:
             await self._loop.run_in_executor(
                 self._background_executor,
                 functools.partial(self._bus.send,
                                   message,
                                   timeout=monotonic_deadline -
                                   self._loop.time()),
             )
         except (asyncio.TimeoutError, can.CanError
                 ):  # CanError is also used to report timeouts (weird).
             break
         else:
             num_sent += 1
             if f.loopback:
                 loopback.append((Timestamp.now(), f))
     if loopback:
         self.loop.call_soon(self._invoke_rx_handler, loopback)
     return num_sent
Ejemplo n.º 12
0
    async def send(self, frames: typing.Iterable[Envelope], monotonic_deadline: float) -> int:
        del monotonic_deadline  # Unused
        if self._closed:
            raise pyuavcan.transport.ResourceClosedError

        if self._raise_on_send_once:
            self._raise_on_send_once, ex = None, self._raise_on_send_once
            assert isinstance(ex, Exception)
            raise ex

        frames = list(frames)
        assert len(frames) > 0, "Interface constraint violation: empty transmission set"
        assert min(map(lambda x: len(x.frame.data), frames)) >= 1, "CAN frames with empty payload are not valid"
        # The media interface spec says that it is guaranteed that the CAN ID is the same across the set; enforce this.
        assert len(set(map(lambda x: x.frame.identifier, frames))) == 1, "Interface constraint violation: nonuniform ID"

        timestamp = Timestamp.now()

        # Broadcast across the virtual bus we're emulating here.
        for p in self._peers:
            if p is not self:
                # Unconditionally clear the loopback flag because for the other side these are
                # regular received frames, not loopback frames.
                p._receive(  # pylint: disable=protected-access
                    (timestamp, Envelope(f.frame, loopback=False)) for f in frames
                )

        # Simple loopback emulation with acceptance filtering.
        self._receive((timestamp, f) for f in frames if f.loopback)
        return len(frames)
Ejemplo n.º 13
0
 def __init__(self, source_node_id: int, extent_bytes: int):
     self._source_node_id = int(source_node_id)
     self._timestamp = Timestamp(0, 0)
     self._transfer_id = 0
     self._toggle_bit = False
     self._max_payload_size_bytes_with_crc = int(extent_bytes) + TRANSFER_CRC_LENGTH_BYTES
     self._crc = pyuavcan.transport.commons.crc.CRC16CCITT()
     self._payload_truncated = False
     self._fragmented_payload: typing.List[memoryview] = []
Ejemplo n.º 14
0
 def proc(monotonic_ns: int, frame: UAVCANFrame) -> typing.Union[None, TransferReassemblyErrorID, TransferFrom]:
     away = rx.process_frame(
         timestamp=Timestamp(system_ns=0, monotonic_ns=monotonic_ns),
         priority=priority,
         frame=frame,
         transfer_id_timeout_ns=transfer_id_timeout_ns,
     )
     assert away is None or isinstance(away, (TransferReassemblyErrorID, TransferFrom))
     return away
Ejemplo n.º 15
0
 def inject_received(
         self, frames: typing.Iterable[typing.Union[Envelope,
                                                    DataFrame]]) -> None:
     timestamp = Timestamp.now()
     self._receive((
         timestamp,
         (f if isinstance(f, Envelope
                          ) else Envelope(frame=f, loopback=False)),
     ) for f in frames)
Ejemplo n.º 16
0
 def sniff_sniff(ts: Timestamp, pack: RawPacket) -> None:
     nonlocal ts_last
     now = Timestamp.now()
     assert ts_last.monotonic_ns <= ts.monotonic_ns <= now.monotonic_ns
     assert ts_last.system_ns <= ts.system_ns <= now.system_ns
     ts_last = ts
     # Make sure that all traffic from foreign networks is filtered out by the sniffer.
     assert (int(pack.ip_header.source)
             & 0x_FFFF_0000) == (int(fac.local_ip_address) & 0x_FFFF_0000)
     sniffs.append(pack)
Ejemplo n.º 17
0
 def sniff_sniff(cap: LinkLayerCapture) -> None:
     nonlocal ts_last
     now = Timestamp.now()
     assert ts_last.monotonic_ns <= cap.timestamp.monotonic_ns <= now.monotonic_ns
     assert ts_last.system_ns <= cap.timestamp.system_ns <= now.system_ns
     ts_last = cap.timestamp
     # Make sure that all traffic from foreign networks is filtered out by the sniffer.
     assert (int(parse_ip(cap.packet).source_destination[0])
             & 0x_FFFF_0000) == (int(fac.local_ip_address) & 0x_FFFF_0000)
     sniffs.append(cap)
Ejemplo n.º 18
0
 async def send_and_wait() -> None:
     ts = Timestamp.now()
     sock_tx.send(b"".join(
         UDPFrame(
             priority=Priority.HIGH,
             transfer_id=0,
             index=0,
             end_of_transfer=True,
             payload=memoryview(str(ts).encode()),
         ).compile_header_and_payload()))
     await (asyncio.sleep(0.5))  # Let the handler run in the background.
Ejemplo n.º 19
0
 def trn(
     monotonic_ns: int, transfer_id: int, fragmented_payload: typing.Sequence[typing.Union[bytes, str, memoryview]]
 ) -> TransferFrom:
     return TransferFrom(
         timestamp=Timestamp(system_ns=0, monotonic_ns=monotonic_ns),
         priority=priority,
         transfer_id=transfer_id,
         fragmented_payload=[
             memoryview(x if isinstance(x, (bytes, memoryview)) else x.encode()) for x in fragmented_payload
         ],
         source_node_id=source_node_id,
     )
Ejemplo n.º 20
0
 def _read_batch(self) -> typing.List[typing.Tuple[Timestamp, Envelope]]:
     batch: typing.List[typing.Tuple[Timestamp, Envelope]] = []
     while not self._closed:
         msg = self._bus.recv(0.0 if batch else self._MAXIMAL_TIMEOUT_SEC)
         if msg is None:
             break
         timestamp = Timestamp.now()  # TODO: use accurate timestamping
         loopback = False  # TODO: no possibility to get real loopback yet
         frame = self._parse_native_frame(msg)
         if frame is not None:
             batch.append((timestamp, Envelope(frame, loopback)))
     return batch
Ejemplo n.º 21
0
def _unittest_frame_compile_message() -> None:
    from pyuavcan.transport import Priority, MessageDataSpecifier, Timestamp

    f = SerialFrame(timestamp=Timestamp.now(),
                    priority=Priority.HIGH,
                    source_node_id=SerialFrame.FRAME_DELIMITER_BYTE,
                    destination_node_id=SerialFrame.ESCAPE_PREFIX_BYTE,
                    data_specifier=MessageDataSpecifier(12345),
                    data_type_hash=0xdead_beef_bad_c0ffe,
                    transfer_id=1234567890123456789,
                    index=1234567,
                    end_of_transfer=True,
                    payload=memoryview(b'abcd\x9Eef\x8E'))

    buffer = bytearray(0 for _ in range(1000))
    mv = f.compile_into(buffer)

    assert mv[0] == SerialFrame.FRAME_DELIMITER_BYTE
    assert mv[-1] == SerialFrame.FRAME_DELIMITER_BYTE
    segment = bytes(mv[1:-1])
    assert SerialFrame.FRAME_DELIMITER_BYTE not in segment

    # Header validation
    assert segment[0] == _VERSION
    assert segment[1] == int(Priority.HIGH)
    assert segment[2] == SerialFrame.ESCAPE_PREFIX_BYTE
    assert (segment[3], segment[4]) == (SerialFrame.FRAME_DELIMITER_BYTE ^ 0xFF, 0)
    assert segment[5] == SerialFrame.ESCAPE_PREFIX_BYTE
    assert (segment[6], segment[7]) == (SerialFrame.ESCAPE_PREFIX_BYTE ^ 0xFF, 0)
    assert segment[8:10] == 12345 .to_bytes(2, 'little')
    assert segment[10:18] == 0xdead_beef_bad_c0ffe .to_bytes(8, 'little')
    assert segment[18:26] == 1234567890123456789 .to_bytes(8, 'little')
    assert segment[26:30] == (1234567 + 0x8000_0000).to_bytes(4, 'little')
    assert segment[30:34] == b'\x00' * 4

    # Payload validation
    assert segment[34:38] == b'abcd'
    assert segment[38] == SerialFrame.ESCAPE_PREFIX_BYTE
    assert segment[39] == 0x9E ^ 0xFF
    assert segment[40:42] == b'ef'
    assert segment[42] == SerialFrame.ESCAPE_PREFIX_BYTE
    assert segment[43] == 0x8E ^ 0xFF

    # CRC validation
    header = SerialFrame.HEADER_STRUCT.pack(_VERSION,
                                            int(f.priority),
                                            f.source_node_id,
                                            f.destination_node_id,
                                            12345,
                                            f.data_type_hash,
                                            f.transfer_id,
                                            f.index + 0x8000_0000)
    assert segment[44:] == pyuavcan.transport.commons.crc.CRC32C.new(header, f.payload).value_as_bytes
Ejemplo n.º 22
0
def _unittest_udp_tracer() -> None:
    from pytest import approx
    from ipaddress import ip_address
    from pyuavcan.transport import Priority, ServiceDataSpecifier
    from pyuavcan.transport.udp import UDPTransport
    from ._ip import MACHeader, IPHeader, UDPHeader, service_data_specifier_to_udp_port

    tr = UDPTransport.make_tracer()
    ts = Timestamp.now()

    ds = ServiceDataSpecifier(11, ServiceDataSpecifier.Role.RESPONSE)
    trace = tr.update(
        UDPCapture(
            ts,
            RawPacket(
                MACHeader(memoryview(b""), memoryview(b"")),
                IPHeader(ip_address("127.0.0.42"), ip_address("127.0.0.63")),
                UDPHeader(12345, service_data_specifier_to_udp_port(ds)),
                memoryview(b"".join(
                    UDPFrame(
                        priority=Priority.SLOW,
                        transfer_id=1234567890,
                        index=0,
                        end_of_transfer=True,
                        payload=memoryview(b"Hello world!"),
                    ).compile_header_and_payload())),
            ),
        ))
    assert isinstance(trace, TransferTrace)
    assert trace.timestamp == ts
    assert trace.transfer_id_timeout == approx(
        AlienTransferReassembler.MAX_TRANSFER_ID_TIMEOUT)  # Initial value.
    assert trace.transfer.metadata.transfer_id == 1234567890
    assert trace.transfer.metadata.priority == Priority.SLOW
    assert trace.transfer.metadata.session_specifier.source_node_id == 42
    assert trace.transfer.metadata.session_specifier.destination_node_id == 63
    assert trace.transfer.metadata.session_specifier.data_specifier == ds
    assert trace.transfer.fragmented_payload == [memoryview(b"Hello world!")]

    assert None is tr.update(
        pyuavcan.transport.Capture(ts))  # Another transport, ignored.

    assert None is tr.update(
        UDPCapture(  # Malformed frame.
            ts,
            RawPacket(
                MACHeader(memoryview(b""), memoryview(b"")),
                IPHeader(ip_address("127.0.0.42"), ip_address("127.1.0.63")),
                UDPHeader(1, 1),
                memoryview(b""),
            ),
        ))
Ejemplo n.º 23
0
    def _finalize(self, known_invalid: bool) -> None:
        if not self._buffer or (len(self._buffer) == 1 and self._buffer[0]
                                == SerialFrame.FRAME_DELIMITER_BYTE):
            # Avoid noise in the OOB output during normal operation.
            # TODO: this is a hack in place of the proper on-the-fly COBS parser.
            return

        buf = memoryview(self._buffer)
        self._buffer = bytearray(
        )  # There are memoryview instances pointing to the old buffer!
        ts = self._timestamp or Timestamp.now()
        self._timestamp = None

        parsed: typing.Optional[SerialFrame] = None
        if (not known_invalid) and len(buf) <= self._max_frame_size_bytes:
            parsed = SerialFrame.parse_from_cobs_image(buf)

        self._callback(ts, buf, parsed)
Ejemplo n.º 24
0
async def _unittest_issue_120() -> None:
    from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer
    from pyuavcan.transport import Priority, Timestamp, OutputSessionSpecifier
    from .media.mock import MockMedia

    asyncio.get_running_loop().slow_callback_duration = 5.0

    peers: typing.Set[MockMedia] = set()
    media = MockMedia(peers, 8, 10)
    tr = can.CANTransport(media, 42)
    assert tr.protocol_parameters.transfer_id_modulo == 32

    feedback_collector = _FeedbackCollector()

    ses = tr.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None),
        PayloadMetadata(1024))
    ses.enable_feedback(feedback_collector.give)
    for i in range(70):
        ts = Timestamp.now()
        assert await ses.send(
            Transfer(
                timestamp=ts,
                priority=Priority.SLOW,
                transfer_id=i,
                fragmented_payload=[_mem(str(i))] *
                7,  # Ensure both single- and multiframe
            ),
            tr.loop.time() + 1.0,
        )
        await asyncio.sleep(0.1)
        fb = feedback_collector.take()
        assert fb.original_transfer_timestamp == ts

    num_frames = (10 * 1) + (60 * 3)  # 10 single-frame, 60 multi-frame
    assert 70 == ses.sample_statistics().transfers
    assert num_frames == ses.sample_statistics().frames
    assert 0 == tr.sample_statistics().in_frames  # loopback not included here
    assert 70 == tr.sample_statistics(
    ).in_frames_loopback  # only first frame of each transfer
    assert num_frames == tr.sample_statistics().out_frames
    assert 70 == tr.sample_statistics(
    ).out_frames_loopback  # only first frame of each transfer
    assert 0 == tr.sample_statistics().lost_loopback_frames
Ejemplo n.º 25
0
def _unittest_can_capture() -> None:
    from pyuavcan.transport import MessageDataSpecifier
    from .media import FrameFormat
    from ._identifier import MessageCANID

    ts = Timestamp.now()
    payload = bytearray(b"123\x0A")
    cap = CANCapture(
        ts,
        DataFrame(
            FrameFormat.EXTENDED,
            MessageCANID(Priority.SLOW, 42,
                         3210).compile([memoryview(payload)]),
            payload,
        ),
        own=True,
    )
    print(cap)
    parsed = cap.parse()
    assert parsed is not None
    ss, prio, uf = parsed
    assert ss.source_node_id == 42
    assert ss.destination_node_id is None
    assert isinstance(ss.data_specifier, MessageDataSpecifier)
    assert ss.data_specifier.subject_id == 3210
    assert prio == Priority.SLOW
    assert uf.transfer_id == 0x0A
    assert uf.padded_payload == b"123"
    assert not uf.start_of_transfer
    assert not uf.end_of_transfer
    assert not uf.toggle_bit

    # Invalid CAN ID
    assert None is CANCapture(
        ts, DataFrame(FrameFormat.BASE, 123, payload), own=True).parse()

    # Invalid CAN payload
    assert (None is CANCapture(
        ts,
        DataFrame(FrameFormat.EXTENDED,
                  MessageCANID(Priority.SLOW, 42, 3210).compile([]),
                  bytearray()),
        own=True,
    ).parse())
Ejemplo n.º 26
0
def _unittest_frame_compile_service() -> None:
    from pyuavcan.transport import Priority, ServiceDataSpecifier, Timestamp

    f = SerialFrame(timestamp=Timestamp.now(),
                    priority=Priority.FAST,
                    source_node_id=SerialFrame.FRAME_DELIMITER_BYTE,
                    destination_node_id=None,
                    data_specifier=ServiceDataSpecifier(123, ServiceDataSpecifier.Role.RESPONSE),
                    data_type_hash=0xdead_beef_bad_c0ffe,
                    transfer_id=1234567890123456789,
                    index=1234567,
                    end_of_transfer=False,
                    payload=memoryview(b''))

    buffer = bytearray(0 for _ in range(50))
    mv = f.compile_into(buffer)

    assert mv[0] == mv[-1] == SerialFrame.FRAME_DELIMITER_BYTE
    segment = bytes(mv[1:-1])
    assert SerialFrame.FRAME_DELIMITER_BYTE not in segment

    # Header validation
    assert segment[0] == _VERSION
    assert segment[1] == int(Priority.FAST)
    assert segment[2] == SerialFrame.ESCAPE_PREFIX_BYTE
    assert (segment[3], segment[4]) == (SerialFrame.FRAME_DELIMITER_BYTE ^ 0xFF, 0)
    assert (segment[5], segment[6]) == (0xFF, 0xFF)
    assert segment[7:9] == ((1 << 15) | (1 << 14) | 123) .to_bytes(2, 'little')
    assert segment[9:17] == 0xdead_beef_bad_c0ffe .to_bytes(8, 'little')
    assert segment[17:25] == 1234567890123456789 .to_bytes(8, 'little')
    assert segment[25:29] == 1234567 .to_bytes(4, 'little')
    assert segment[29:33] == b'\x00' * 4

    # CRC validation
    header = SerialFrame.HEADER_STRUCT.pack(_VERSION,
                                            int(f.priority),
                                            f.source_node_id,
                                            _ANONYMOUS_NODE_ID,
                                            (1 << 15) | (1 << 14) | 123,
                                            f.data_type_hash,
                                            f.transfer_id,
                                            f.index)
    assert segment[33:] == pyuavcan.transport.commons.crc.CRC32C.new(header, f.payload).value_as_bytes
Ejemplo n.º 27
0
    def _reader_thread_func(self) -> None:
        in_bytes_count = 0

        def callback(ts: Timestamp, buf: memoryview,
                     frame: typing.Optional[SerialFrame]) -> None:
            item = buf if frame is None else frame
            self._loop.call_soon_threadsafe(
                self._handle_received_item_and_update_stats, ts, item,
                in_bytes_count)
            if self._capture_handlers:
                pyuavcan.util.broadcast(self._capture_handlers)(SerialCapture(
                    ts, SerialCapture.Direction.RX, buf))

        try:
            parser = StreamParser(callback, max(self.VALID_MTU_RANGE))
            assert abs(self._serial_port.timeout -
                       _SERIAL_PORT_READ_TIMEOUT) < 0.1

            while not self._closed and self._serial_port.is_open:
                chunk = self._serial_port.read(
                    max(1, self._serial_port.inWaiting()))
                chunk_ts = Timestamp.now()
                in_bytes_count += len(chunk)
                parser.process_next_chunk(chunk, chunk_ts)

        except Exception as ex:  # pragma: no cover
            if self._closed or not self._serial_port.is_open:
                _logger.debug(
                    "%s: The serial port is closed, exception ignored: %r",
                    self, ex)
            else:
                _logger.exception(
                    "%s: Reader thread has failed, the instance with port %s will be terminated: %s",
                    self,
                    self._serial_port,
                    ex,
                )
            self._closed = True
            self._serial_port.close()

        finally:
            _logger.debug("%s: Reader thread is exiting. Head aega.", self)
def _unittest_serialize_transfer() -> None:
    from pyuavcan.transport import Priority, Timestamp

    timestamp = Timestamp.now()
    priority = Priority.NOMINAL
    transfer_id = 12345678901234567890

    def construct_frame(index: int, end_of_transfer: bool,
                        payload: memoryview) -> Frame:
        return Frame(timestamp=timestamp,
                     priority=priority,
                     transfer_id=transfer_id,
                     index=index,
                     end_of_transfer=end_of_transfer,
                     payload=payload)

    assert [
        construct_frame(0, True, memoryview(b'hello world')),
    ] == list(
        serialize_transfer(
            [memoryview(b'hello'),
             memoryview(b' '),
             memoryview(b'world')], 100, construct_frame))

    assert [
        construct_frame(0, True, memoryview(b'')),
    ] == list(serialize_transfer([], 100, construct_frame))

    hello_world_crc = pyuavcan.transport.commons.crc.CRC32C()
    hello_world_crc.add(b'hello world')

    assert [
        construct_frame(0, False, memoryview(b'hello')),
        construct_frame(1, False, memoryview(b' worl')),
        construct_frame(2, True,
                        memoryview(b'd' + hello_world_crc.value_as_bytes)),
    ] == list(
        serialize_transfer(
            [memoryview(b'hello'),
             memoryview(b' '),
             memoryview(b'world')], 5, construct_frame))
Ejemplo n.º 29
0
    def _read_frame(self, ts_mono_ns: int) -> typing.Tuple[Timestamp, Envelope]:
        while True:
            data, ancdata, msg_flags, _addr = self._sock.recvmsg(
                self._native_frame_size, self._ancillary_data_buffer_size
            )
            assert msg_flags & socket.MSG_TRUNC == 0, "The data buffer is not large enough"
            assert msg_flags & socket.MSG_CTRUNC == 0, "The ancillary data buffer is not large enough"

            loopback = bool(msg_flags & socket.MSG_CONFIRM)
            ts_system_ns = 0
            for cmsg_level, cmsg_type, cmsg_data in ancdata:
                if cmsg_level == socket.SOL_SOCKET and cmsg_type == _SO_TIMESTAMP:
                    sec, usec = _TIMEVAL_STRUCT.unpack(cmsg_data)
                    ts_system_ns = (sec * 1_000_000 + usec) * 1000
                else:
                    assert False, f"Unexpected ancillary data: {cmsg_level}, {cmsg_type}, {cmsg_data!r}"

            assert ts_system_ns > 0, "Missing the timestamp; does the driver support timestamping?"
            timestamp = Timestamp(system_ns=ts_system_ns, monotonic_ns=ts_mono_ns)
            out = SocketCANMedia._parse_native_frame(data)
            if out is not None:
                return timestamp, Envelope(out, loopback=loopback)
Ejemplo n.º 30
0
def _unittest_frame_compile_message() -> None:
    from pyuavcan.transport import Priority, MessageDataSpecifier, Timestamp

    f = SerialFrame(timestamp=Timestamp.now(),
                    priority=Priority.HIGH,
                    source_node_id=SerialFrame.FRAME_DELIMITER_BYTE,
                    destination_node_id=SerialFrame.FRAME_DELIMITER_BYTE,
                    data_specifier=MessageDataSpecifier(12345),
                    data_type_hash=0xdead_beef_bad_c0ffe,
                    transfer_id=1234567890123456789,
                    index=1234567,
                    end_of_transfer=True,
                    payload=memoryview(b'abcd\x00ef\x00'))

    buffer = bytearray(0 for _ in range(1000))
    mv = f.compile_into(buffer)

    assert mv[0] == SerialFrame.FRAME_DELIMITER_BYTE
    assert mv[-1] == SerialFrame.FRAME_DELIMITER_BYTE

    segment_cobs = bytes(mv[1:-1])
    assert SerialFrame.FRAME_DELIMITER_BYTE not in segment_cobs

    segment = cobs.decode(segment_cobs)

    # Header validation
    assert segment[0] == _VERSION
    assert segment[1] == int(Priority.HIGH)
    assert (segment[2], segment[3]) == (SerialFrame.FRAME_DELIMITER_BYTE, 0)
    assert (segment[4], segment[5]) == (SerialFrame.FRAME_DELIMITER_BYTE, 0)
    assert segment[6:8] == 12345 .to_bytes(2, 'little')
    assert segment[8:16] == 0xdead_beef_bad_c0ffe .to_bytes(8, 'little')
    assert segment[16:24] == 1234567890123456789 .to_bytes(8, 'little')
    assert segment[24:28] == (1234567 + 0x8000_0000).to_bytes(4, 'little')
    # Header CRC here

    # Payload validation
    assert segment[32:40] == b'abcd\x00ef\x00'
    assert segment[40:] == pyuavcan.transport.commons.crc.CRC32C.new(f.payload).value_as_bytes