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.º 2
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.º 3
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.º 4
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.º 5
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.º 6
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.º 7
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.º 8
0
async def _unittest_slow_presentation_rpc(
        generated_packages: typing.List[pyuavcan.dsdl.GeneratedPackageInfo],
        transport_factory: TransportFactory) -> None:
    assert generated_packages
    import uavcan.register
    import uavcan.primitive
    import uavcan.time
    from pyuavcan.transport import Priority, Timestamp

    tran_a, tran_b, _ = transport_factory(123, 42)
    assert tran_a.local_node_id == 123
    assert tran_b.local_node_id == 42

    pres_a = pyuavcan.presentation.Presentation(tran_a)
    pres_b = pyuavcan.presentation.Presentation(tran_b)

    assert pres_a.transport is tran_a

    server = pres_a.get_server_with_fixed_service_id(
        uavcan.register.Access_1_0)
    assert server is pres_a.get_server_with_fixed_service_id(
        uavcan.register.Access_1_0)

    client0 = pres_b.make_client_with_fixed_service_id(
        uavcan.register.Access_1_0, 123)
    client1 = pres_b.make_client_with_fixed_service_id(
        uavcan.register.Access_1_0, 123)
    client_dead = pres_b.make_client_with_fixed_service_id(
        uavcan.register.Access_1_0, 111)
    assert client0 is not client1
    assert client0._maybe_impl is not None
    assert client1._maybe_impl is not None
    assert client0._maybe_impl is client1._maybe_impl
    assert client0._maybe_impl is not client_dead._maybe_impl
    assert client0._maybe_impl.proxy_count == 2
    assert client_dead._maybe_impl is not None
    assert client_dead._maybe_impl.proxy_count == 1

    with pytest.raises(TypeError):
        # noinspection PyTypeChecker
        pres_a.make_publisher_with_fixed_subject_id(
            uavcan.register.Access_1_0)  # type: ignore
    with pytest.raises(TypeError):
        # noinspection PyTypeChecker
        pres_a.make_subscriber_with_fixed_subject_id(
            uavcan.register.Access_1_0)  # type: ignore

    assert client0.response_timeout == pytest.approx(1.0)
    client0.response_timeout = 0.1
    assert client0.response_timeout == pytest.approx(0.1)
    client0.priority = Priority.SLOW

    last_request = uavcan.register.Access_1_0.Request()
    last_metadata = pyuavcan.presentation.ServiceRequestMetadata(
        timestamp=Timestamp(0, 0),
        priority=Priority(0),
        transfer_id=0,
        client_node_id=0)
    response: typing.Optional[uavcan.register.Access_1_0.Response] = None

    async def server_handler(request: uavcan.register.Access_1_0.Request,
                             metadata: pyuavcan.presentation.ServiceRequestMetadata) \
            -> typing.Optional[uavcan.register.Access_1_0.Response]:
        nonlocal last_metadata
        print('SERVICE REQUEST:', request, metadata)
        assert isinstance(request, server.dtype.Request) and isinstance(
            request, uavcan.register.Access_1_0.Request)
        assert repr(last_request) == repr(request)
        last_metadata = metadata
        return response

    server.serve_in_background(server_handler)

    last_request = uavcan.register.Access_1_0.Request(
        name=uavcan.register.Name_1_0('Hello world!'),
        value=uavcan.register.Value_1_0(string=uavcan.primitive.String_1_0(
            'Profanity will not be tolerated')))
    result_a = await client0.call(last_request)
    assert result_a is None, 'Expected to fail'
    assert last_metadata.client_node_id == 42
    assert last_metadata.transfer_id == 0
    assert last_metadata.priority == Priority.SLOW

    client0.response_timeout = 2.0  # Increase the timeout back because otherwise the test fails on slow systems.

    last_request = uavcan.register.Access_1_0.Request(
        name=uavcan.register.Name_1_0('security.uber_secure_password'))
    response = uavcan.register.Access_1_0.Response(
        timestamp=uavcan.time.SynchronizedTimestamp_1_0(123456789),
        mutable=True,
        persistent=False,
        value=uavcan.register.Value_1_0(
            string=uavcan.primitive.String_1_0('hunter2')))
    client0.priority = Priority.IMMEDIATE
    result_b = (await client0.call(last_request))[0]  # type: ignore
    assert repr(result_b) == repr(response)
    assert last_metadata.client_node_id == 42
    assert last_metadata.transfer_id == 1
    assert last_metadata.priority == Priority.IMMEDIATE

    server.close()
    client0.close()
    client1.close()
    client_dead.close()
    # Double-close has no effect (no error either):
    server.close()
    client0.close()
    client1.close()
    client_dead.close()

    # Allow the tasks to finish
    await asyncio.sleep(0.1)

    # Make sure the transport sessions have been closed properly, this is supremely important.
    assert list(pres_a.transport.input_sessions) == []
    assert list(pres_b.transport.input_sessions) == []
    assert list(pres_a.transport.output_sessions) == []
    assert list(pres_b.transport.output_sessions) == []
Ejemplo n.º 9
0
 def mk_ts(monotonic: float) -> Timestamp:
     monotonic_ns = round(monotonic * 1e9)
     return Timestamp(system_ns=monotonic_ns + 10**12,
                      monotonic_ns=monotonic_ns)