Ejemplo n.º 1
0
def session_from_dcs(ses: Session) -> AlienSessionSpecifier:
    from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier

    if ses.subject:
        try:
            source_node_id = ses.subject.source[0].value
        except LookupError:
            source_node_id = None
        return AlienSessionSpecifier(
            source_node_id=source_node_id,
            destination_node_id=None,
            data_specifier=MessageDataSpecifier(
                subject_id=ses.subject.subject_id.value),
        )

    if ses.service:
        return AlienSessionSpecifier(
            source_node_id=ses.service.source.value,
            destination_node_id=ses.service.destination.value,
            data_specifier=ServiceDataSpecifier(
                service_id=ses.service.service_id.value,
                role=ServiceDataSpecifier.Role.REQUEST if
                ses.service.is_request else ServiceDataSpecifier.Role.RESPONSE,
            ),
        )

    assert False
Ejemplo n.º 2
0
    def update(self, cap: Capture) -> typing.Optional[Trace]:
        """
        If the capture encapsulates more than one serialized frame, a :class:`ValueError` will be raised.
        To avoid this, always ensure that the captured fragments are split on the frame delimiters
        (which are simply zero bytes).
        Captures provided by PyUAVCAN are always fragmented correctly, but you may need to implement fragmentation
        manually when reading data from an external file.
        """
        if not isinstance(cap, SerialCapture):
            return None

        self._parsers[cap.direction].process_next_chunk(
            cap.fragment, cap.timestamp)
        if self._parser_output is None:
            return None

        timestamp, item = self._parser_output
        self._parser_output = None
        if isinstance(item, memoryview):
            return SerialOutOfBandTrace(timestamp, item)

        if isinstance(item, SerialFrame):
            spec = AlienSessionSpecifier(
                source_node_id=item.source_node_id,
                destination_node_id=item.destination_node_id,
                data_specifier=item.data_specifier,
            )
            return self._get_session(spec).update(timestamp, item)

        assert False
Ejemplo n.º 3
0
async def _unittest_loopback_spoofing() -> None:
    from pyuavcan.transport import AlienTransfer, AlienSessionSpecifier, AlienTransferMetadata, Priority
    from pyuavcan.transport import MessageDataSpecifier
    from pyuavcan.transport.loopback import LoopbackCapture

    tr = pyuavcan.transport.loopback.LoopbackTransport(None)

    mon_events: typing.List[pyuavcan.transport.Capture] = []
    tr.begin_capture(mon_events.append)

    transfer = AlienTransfer(
        AlienTransferMetadata(
            Priority.IMMEDIATE, 54321,
            AlienSessionSpecifier(1234, None, MessageDataSpecifier(7777))),
        fragmented_payload=[],
    )
    assert tr.spoof_result  # Success is default.
    assert await tr.spoof(transfer,
                          monotonic_deadline=asyncio.get_running_loop().time())
    cap = mon_events.pop()
    assert isinstance(cap, LoopbackCapture)
    assert cap.transfer == transfer

    tr.spoof_result = False
    assert not await tr.spoof(
        transfer, monotonic_deadline=asyncio.get_running_loop().time() + 0.5)
    assert not mon_events

    tr.spoof_result = RuntimeError("Intended error")
    assert isinstance(tr.spoof_result, RuntimeError)
    with pytest.raises(RuntimeError, match="Intended error"):
        await tr.spoof(transfer,
                       monotonic_deadline=asyncio.get_running_loop().time() +
                       0.5)
    assert not mon_events
Ejemplo n.º 4
0
async def _unittest_serial_spoofing() -> None:
    from pyuavcan.transport import AlienTransfer, AlienSessionSpecifier, AlienTransferMetadata, Priority
    from pyuavcan.transport import MessageDataSpecifier

    tr = pyuavcan.transport.serial.SerialTransport("loop://", None, mtu=1024)

    mon_events: typing.List[pyuavcan.transport.Capture] = []
    assert not tr.capture_active
    tr.begin_capture(mon_events.append)
    assert tr.capture_active

    transfer = AlienTransfer(
        AlienTransferMetadata(
            Priority.IMMEDIATE, 0xBADC0FFEE0DDF00D,
            AlienSessionSpecifier(1234, None, MessageDataSpecifier(7777))),
        fragmented_payload=[],
    )
    assert await tr.spoof(
        transfer, monotonic_deadline=asyncio.get_running_loop().time() + 5.0)
    await asyncio.sleep(1.0)
    cap_rx, cap_tx = sorted(mon_events,
                            key=lambda x: typing.cast(SerialCapture, x).own)
    assert isinstance(cap_rx, SerialCapture)
    assert isinstance(cap_tx, SerialCapture)
    assert not cap_rx.own and cap_tx.own
    assert cap_tx.fragment.tobytes() == cap_rx.fragment.tobytes()
    assert 0xBADC0FFEE0DDF00D.to_bytes(8,
                                       "little") in cap_rx.fragment.tobytes()
    assert 1234.to_bytes(2, "little") in cap_rx.fragment.tobytes()
    assert 7777.to_bytes(2, "little") in cap_rx.fragment.tobytes()

    with pytest.raises(
            pyuavcan.transport.OperationNotDefinedForAnonymousNodeError,
            match=r".*multi-frame.*"):
        transfer = AlienTransfer(
            AlienTransferMetadata(
                Priority.IMMEDIATE, 0xBADC0FFEE0DDF00D,
                AlienSessionSpecifier(None, None, MessageDataSpecifier(7777))),
            fragmented_payload=[memoryview(bytes(range(256)))] * 5,
        )
        assert await tr.spoof(
            transfer, monotonic_deadline=asyncio.get_running_loop().time())
Ejemplo n.º 5
0
def _unittest_session_spec() -> None:
    import pytest
    from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier

    ss = AlienSessionSpecifier(
        source_node_id=None,
        destination_node_id=None,
        data_specifier=MessageDataSpecifier(6666),
    )
    assert session_from_dcs(session_to_dcs(ss)) == ss

    ss = AlienSessionSpecifier(
        source_node_id=123,
        destination_node_id=None,
        data_specifier=MessageDataSpecifier(6666),
    )
    assert session_from_dcs(session_to_dcs(ss)) == ss

    ss = AlienSessionSpecifier(
        source_node_id=123,
        destination_node_id=321,
        data_specifier=ServiceDataSpecifier(222,
                                            ServiceDataSpecifier.Role.REQUEST),
    )
    assert session_from_dcs(session_to_dcs(ss)) == ss

    ss = AlienSessionSpecifier(
        source_node_id=123,
        destination_node_id=321,
        data_specifier=ServiceDataSpecifier(
            222, ServiceDataSpecifier.Role.RESPONSE),
    )
    assert session_from_dcs(session_to_dcs(ss)) == ss

    with pytest.raises(ValueError):
        session_to_dcs(
            AlienSessionSpecifier(
                source_node_id=None,
                destination_node_id=None,
                data_specifier=ServiceDataSpecifier(
                    222, ServiceDataSpecifier.Role.RESPONSE),
            ))
Ejemplo n.º 6
0
 def parse(self) -> typing.Optional[typing.Tuple[AlienSessionSpecifier, Priority, UAVCANFrame]]:
     uf = UAVCANFrame.parse(self.frame)
     if not uf:
         return None
     ci = CANID.parse(self.frame.identifier)
     if not ci:
         return None
     ss = AlienSessionSpecifier(
         source_node_id=ci.source_node_id,
         destination_node_id=ci.get_destination_node_id(),
         data_specifier=ci.data_specifier,
     )
     return ss, ci.priority, uf
Ejemplo n.º 7
0
async def _unittest_loopback_tracer() -> None:
    from pyuavcan.transport import AlienTransfer, AlienSessionSpecifier, AlienTransferMetadata, Timestamp, Priority
    from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, TransferTrace
    from pyuavcan.transport.loopback import LoopbackCapture

    tr = pyuavcan.transport.loopback.LoopbackTransport.make_tracer()
    ts = Timestamp.now()

    # MESSAGE
    msg = AlienTransfer(
        AlienTransferMetadata(
            Priority.IMMEDIATE, 54321,
            AlienSessionSpecifier(1234, None, MessageDataSpecifier(7777))),
        [],
    )
    assert tr.update(LoopbackCapture(ts, msg)) == TransferTrace(
        timestamp=ts,
        transfer=msg,
        transfer_id_timeout=0.0,
    )

    # REQUEST
    req = AlienTransfer(
        AlienTransferMetadata(
            Priority.NOMINAL,
            333333333,
            AlienSessionSpecifier(
                321, 123,
                ServiceDataSpecifier(222, ServiceDataSpecifier.Role.REQUEST)),
        ),
        [],
    )
    trace_req = tr.update(LoopbackCapture(ts, req))
    assert isinstance(trace_req, TransferTrace)
    assert trace_req == TransferTrace(
        timestamp=ts,
        transfer=req,
        transfer_id_timeout=0.0,
    )

    # RESPONSE
    res = AlienTransfer(
        AlienTransferMetadata(
            Priority.NOMINAL,
            333333333,
            AlienSessionSpecifier(
                123, 444,
                ServiceDataSpecifier(222, ServiceDataSpecifier.Role.RESPONSE)),
        ),
        [],
    )
    assert tr.update(LoopbackCapture(ts, res)) == TransferTrace(
        timestamp=ts,
        transfer=res,
        transfer_id_timeout=0.0,
    )

    # RESPONSE
    res = AlienTransfer(
        AlienTransferMetadata(
            Priority.NOMINAL,
            333333333,
            AlienSessionSpecifier(
                123, 321,
                ServiceDataSpecifier(222, ServiceDataSpecifier.Role.RESPONSE)),
        ),
        [],
    )
    assert tr.update(LoopbackCapture(ts, res)) == TransferTrace(
        timestamp=ts,
        transfer=res,
        transfer_id_timeout=0.0,
    )

    # Unknown capture types should yield None.
    assert tr.update(pyuavcan.transport.Capture(ts)) is None
Ejemplo n.º 8
0
async def _unittest_redundant_transport_capture() -> None:
    from threading import Lock
    from pyuavcan.transport import Capture, Trace, TransferTrace, Priority, ServiceDataSpecifier
    from pyuavcan.transport import AlienTransfer, AlienTransferMetadata, AlienSessionSpecifier
    from pyuavcan.transport.redundant import RedundantDuplicateTransferTrace, RedundantCapture
    from tests.transport.can.media.mock import MockMedia as CANMockMedia

    asyncio.get_event_loop().slow_callback_duration = 5.0

    tracer = RedundantTransport.make_tracer()
    traces: typing.List[typing.Optional[Trace]] = []
    lock = Lock()

    def handle_capture(cap: Capture) -> None:
        with lock:
            # Drop TX frames, they are not interesting for this test.
            assert isinstance(cap, RedundantCapture)
            if isinstance(cap.inferior, pyuavcan.transport.serial.SerialCapture
                          ) and cap.inferior.own:
                return
            if isinstance(
                    cap.inferior,
                    pyuavcan.transport.can.CANCapture) and cap.inferior.own:
                return
            print("CAPTURE:", cap)
            traces.append(tracer.update(cap))

    async def wait(how_many: int) -> None:
        for _ in range(10):
            await asyncio.sleep(0.1)
            with lock:
                if len(traces) >= how_many:
                    return
        assert False, "No traces received"

    # Setup capture -- one is added before capture started, the other is added later.
    # Make sure they are treated identically.
    tr = RedundantTransport()
    inf_a: pyuavcan.transport.Transport = SerialTransport(SERIAL_URI, 1234)
    inf_b: pyuavcan.transport.Transport = SerialTransport(SERIAL_URI, 1234)
    tr.attach_inferior(inf_a)
    assert not tr.capture_active
    assert not inf_a.capture_active
    assert not inf_b.capture_active
    tr.begin_capture(handle_capture)
    assert tr.capture_active
    assert inf_a.capture_active
    assert not inf_b.capture_active
    tr.attach_inferior(inf_b)
    assert tr.capture_active
    assert inf_a.capture_active
    assert inf_b.capture_active

    # Send a transfer and make sure it is handled and deduplicated correctly.
    transfer = AlienTransfer(
        AlienTransferMetadata(
            priority=Priority.IMMEDIATE,
            transfer_id=1234,
            session_specifier=AlienSessionSpecifier(
                source_node_id=321,
                destination_node_id=222,
                data_specifier=ServiceDataSpecifier(
                    77, ServiceDataSpecifier.Role.REQUEST),
            ),
        ),
        [memoryview(b"hello")],
    )
    assert await tr.spoof(transfer,
                          monotonic_deadline=asyncio.get_event_loop().time() +
                          1.0)
    await wait(2)
    with lock:
        # Check the status of the deduplication process. We should get two: one transfer, one duplicate.
        assert len(traces) == 2
        trace = traces.pop(0)
        assert isinstance(trace, TransferTrace)
        assert trace.transfer == transfer
        # This is the duplicate.
        assert isinstance(traces.pop(0), RedundantDuplicateTransferTrace)
        assert not traces

    # Spoof the same thing again, get nothing out: transfers discarded by the inferior's own reassemblers.
    # WARNING: this will fail if too much time has passed since the previous transfer due to TID timeout.
    assert await tr.spoof(transfer,
                          monotonic_deadline=asyncio.get_event_loop().time() +
                          1.0)
    await wait(2)
    with lock:
        assert None is traces.pop(0)
        assert None is traces.pop(0)
        assert not traces

    # But if we change ONLY destination, deduplication will not take place.
    transfer = AlienTransfer(
        AlienTransferMetadata(
            priority=Priority.IMMEDIATE,
            transfer_id=1234,
            session_specifier=AlienSessionSpecifier(
                source_node_id=321,
                destination_node_id=333,
                data_specifier=ServiceDataSpecifier(
                    77, ServiceDataSpecifier.Role.REQUEST),
            ),
        ),
        [memoryview(b"hello")],
    )
    assert await tr.spoof(transfer,
                          monotonic_deadline=asyncio.get_event_loop().time() +
                          1.0)
    await wait(2)
    with lock:
        # Check the status of the deduplication process. We should get two: one transfer, one duplicate.
        assert len(traces) == 2
        trace = traces.pop(0)
        assert isinstance(trace, TransferTrace)
        assert trace.transfer == transfer
        # This is the duplicate.
        assert isinstance(traces.pop(0), RedundantDuplicateTransferTrace)
        assert not traces

    # Change the inferior configuration and make sure it is handled properly.
    tr.detach_inferior(inf_a)
    tr.detach_inferior(inf_b)
    inf_a.close()
    inf_b.close()
    # The new inferiors use cyclic transfer-ID; the tracer should reconfigure itself automatically!
    can_peers: typing.Set[CANMockMedia] = set()
    inf_a = CANTransport(CANMockMedia(can_peers, 64, 2), 111)
    inf_b = CANTransport(CANMockMedia(can_peers, 64, 2), 111)
    tr.attach_inferior(inf_a)
    tr.attach_inferior(inf_b)
    # Capture should have been launched automatically.
    assert inf_a.capture_active
    assert inf_b.capture_active

    # Send transfer over CAN and observe that it is handled well.
    transfer = AlienTransfer(
        AlienTransferMetadata(
            priority=Priority.IMMEDIATE,
            transfer_id=19,
            session_specifier=AlienSessionSpecifier(
                source_node_id=111,
                destination_node_id=22,
                data_specifier=ServiceDataSpecifier(
                    77, ServiceDataSpecifier.Role.REQUEST),
            ),
        ),
        [memoryview(b"hello")],
    )
    assert await tr.spoof(transfer,
                          monotonic_deadline=asyncio.get_event_loop().time() +
                          1.0)
    await wait(2)
    with lock:
        # Check the status of the deduplication process. We should get two: one transfer, one duplicate.
        assert len(traces) == 2
        trace = traces.pop(0)
        assert isinstance(trace, TransferTrace)
        assert trace.transfer == transfer
        # This is the duplicate.
        assert isinstance(traces.pop(0), RedundantDuplicateTransferTrace)
        assert not traces

    # Dispose of everything.
    tr.close()
    await asyncio.sleep(1.0)
Ejemplo n.º 9
0
def _unittest_can_alien_session() -> None:
    from pytest import approx
    from pyuavcan.transport import MessageDataSpecifier
    from ._identifier import MessageCANID

    ts = Timestamp.now()
    can_identifier = MessageCANID(Priority.SLOW, 42, 3210).compile([])

    def frm(
        padded_payload: typing.Union[bytes, str],
        transfer_id: int,
        start_of_transfer: bool,
        end_of_transfer: bool,
        toggle_bit: bool,
    ) -> UAVCANFrame:
        return UAVCANFrame(
            identifier=can_identifier,
            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,
        )

    spec = AlienSessionSpecifier(42, None, MessageDataSpecifier(3210))
    ses = _AlienSession(spec)

    # Valid multi-frame (test data copy-posted from the reassembler test).
    assert None is ses.update(
        ts, Priority.HIGH,
        frm(b"\x00\x01\x02\x03\x04\x05\x06", 11, True, False, True))
    assert None is ses.update(
        ts, Priority.HIGH,
        frm(b"\x07\x08\x09\x0a\x0b\x0c\x0d", 11, False, False, False))
    assert None is ses.update(
        ts, Priority.HIGH,
        frm(b"\x0e\x0f\x10\x11\x12\x13\x14", 11, False, False, True))
    assert None is ses.update(
        ts, Priority.HIGH,
        frm(b"\x15\x16\x17\x18\x19\x1a\x1b", 11, False, False, False))
    tr = ses.update(ts, Priority.HIGH,
                    frm(b"\x1c\x1d\x35\x54", 11, False, True, True))
    assert isinstance(tr, TransferTrace)
    assert list(tr.transfer.fragmented_payload) == [
        b"\x00\x01\x02\x03\x04\x05\x06",
        b"\x07\x08\x09\x0a\x0b\x0c\x0d",
        b"\x0e\x0f\x10\x11\x12\x13\x14",
        b"\x15\x16\x17\x18\x19\x1a\x1b",
        b"\x1c\x1d",  # CRC stripped
    ]
    assert tr.transfer.metadata.priority == Priority.HIGH
    assert tr.transfer.metadata.transfer_id == 11
    assert tr.transfer.metadata.session_specifier.source_node_id == 42
    assert tr.transfer.metadata.session_specifier.destination_node_id is None
    assert isinstance(tr.transfer.metadata.session_specifier.data_specifier,
                      MessageDataSpecifier)
    assert tr.transfer.metadata.session_specifier.data_specifier.subject_id == 3210
    assert tr.timestamp == ts
    assert tr.transfer_id_timeout == approx(2.0)  # Default value.

    # Missed start of transfer.
    tr = ses.update(ts, Priority.HIGH, frm(b"123456", 2, False, False, False))
    assert isinstance(tr, CANErrorTrace)

    # Valid single-frame; TID timeout updated.
    tr = ses.update(ts, Priority.LOW,
                    frm(b"\x00\x01\x02\x03\x04\x05\x06", 12, True, True, True))
    assert isinstance(tr, TransferTrace)
    assert tr.transfer.metadata.priority == Priority.LOW
    assert tr.transfer.metadata.transfer_id == 12
    assert tr.transfer.metadata.session_specifier.source_node_id == 42
    assert tr.transfer.metadata.session_specifier.destination_node_id is None
    assert isinstance(tr.transfer.metadata.session_specifier.data_specifier,
                      MessageDataSpecifier)
    assert tr.transfer.metadata.session_specifier.data_specifier.subject_id == 3210
    assert tr.timestamp == ts
    assert ses.transfer_id_timeout == approx(
        1.0)  # Shrunk twice because we're using the same timestamp here.
Ejemplo n.º 10
0
async def _unittest_can_spoofing() -> None:
    from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, Priority, Timestamp
    from pyuavcan.transport import AlienTransfer, AlienSessionSpecifier, AlienTransferMetadata
    from pyuavcan.transport.can._identifier import CANID
    from .media.mock import MockMedia

    asyncio.get_running_loop().slow_callback_duration = 5.0

    peers: typing.Set[MockMedia] = set()
    peeper = MockMedia(peers, 64, 1)
    tr = can.CANTransport(MockMedia(peers, 64, 1), None)

    peeped: typing.List[can.media.DataFrame] = []

    def on_peep(args: typing.Sequence[typing.Tuple[Timestamp, can.media.Envelope]]) -> None:
        nonlocal peeped
        peeped += [e.frame for _ts, e in args]

    peeper.start(on_peep, no_automatic_retransmission=False)
    peeper.configure_acceptance_filters([can.media.FilterConfiguration.new_promiscuous(None)])

    transfer = AlienTransfer(
        AlienTransferMetadata(
            priority=Priority.FAST,
            transfer_id=13107,  # -> 19
            session_specifier=AlienSessionSpecifier(
                source_node_id=0x77,
                destination_node_id=None,
                data_specifier=MessageDataSpecifier(6666),
            ),
        ),
        fragmented_payload=[_mem("123")],
    )
    assert await tr.spoof(transfer, tr.loop.time() + 1.0)
    peep = peeped.pop()
    assert not peeped
    can_id = CANID.parse(peep.identifier)
    assert can_id
    assert can_id.data_specifier == MessageDataSpecifier(6666)
    assert can_id.priority == Priority.FAST
    assert can_id.source_node_id == 0x77
    assert can_id.get_destination_node_id() is None
    assert peep.data[:-1] == b"123"
    assert peep.data[-1] == 0b1110_0000 | 19

    transfer = AlienTransfer(
        AlienTransferMetadata(
            priority=Priority.SLOW,
            transfer_id=1,
            session_specifier=AlienSessionSpecifier(
                source_node_id=0x77,
                destination_node_id=0x66,
                data_specifier=ServiceDataSpecifier(99, role=ServiceDataSpecifier.Role.REQUEST),
            ),
        ),
        fragmented_payload=[_mem("321")],
    )
    assert await tr.spoof(transfer, tr.loop.time() + 1.0)
    peep = peeped.pop()
    assert not peeped
    can_id = CANID.parse(peep.identifier)
    assert can_id
    assert can_id.data_specifier == transfer.metadata.session_specifier.data_specifier
    assert can_id.priority == Priority.SLOW
    assert can_id.source_node_id == 0x77
    assert can_id.get_destination_node_id() == 0x66
    assert peep.data[:-1] == b"321"
    assert peep.data[-1] == 0b1110_0000 | 1

    with pytest.raises(pyuavcan.transport.TransportError):
        await tr.spoof(
            AlienTransfer(
                AlienTransferMetadata(
                    priority=Priority.FAST,
                    transfer_id=13107,
                    session_specifier=AlienSessionSpecifier(
                        source_node_id=123,
                        destination_node_id=123,
                        data_specifier=MessageDataSpecifier(6666),
                    ),
                ),
                fragmented_payload=[],
            ),
            tr.loop.time() + 1.0,
        )

    with pytest.raises(pyuavcan.transport.TransportError):
        await tr.spoof(
            AlienTransfer(
                AlienTransferMetadata(
                    priority=Priority.FAST,
                    transfer_id=13107,
                    session_specifier=AlienSessionSpecifier(
                        source_node_id=0x77,
                        destination_node_id=None,
                        data_specifier=ServiceDataSpecifier(99, role=ServiceDataSpecifier.Role.REQUEST),
                    ),
                ),
                fragmented_payload=[],
            ),
            tr.loop.time() + 1.0,
        )

    with pytest.raises(pyuavcan.transport.TransportError):
        await tr.spoof(
            AlienTransfer(
                AlienTransferMetadata(
                    priority=Priority.FAST,
                    transfer_id=13107,
                    session_specifier=AlienSessionSpecifier(
                        source_node_id=None,
                        destination_node_id=None,
                        data_specifier=MessageDataSpecifier(6666),
                    ),
                ),
                fragmented_payload=[memoryview(bytes(range(256)))],
            ),
            tr.loop.time() + 1.0,
        )