Beispiel #1
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
Beispiel #2
0
    async def _on_spoof_message(
            self, msg: DCSSpoof,
            transfer: pyuavcan.transport.TransferFrom) -> None:
        _logger.debug("Spoofing %s %s over %d ifaces", transfer, msg,
                      len(self._inferiors))
        ss = session_from_dcs(msg.session)

        if msg.transfer_id.size:
            transfer_id = int(msg.transfer_id[0])
        else:
            transfer_id = self._transfer_id_map[ss].get_then_increment()

        # noinspection PyArgumentList
        atr = AlienTransfer(
            metadata=AlienTransferMetadata(
                priority=pyuavcan.transport.Priority(msg.priority.value),
                transfer_id=transfer_id,
                session_specifier=ss,
            ),
            fragmented_payload=[memoryview(msg.payload.payload)],
        )

        inferiors: typing.Iterable[_Inferior]
        if msg.iface_id.size:
            try:
                inferiors = (self._inferiors[int(msg.iface_id[0])], )
            except LookupError:
                inferiors = []  # No such interface -- do nothing.
        else:
            inferiors = self._inferiors.values()
        monotonic_deadline = asyncio.get_event_loop().time(
        ) + msg.timeout.second
        for inf in inferiors:
            inf.push(atr, monotonic_deadline)
Beispiel #3
0
 def update(self, timestamp: Timestamp, frame: UDPFrame) -> typing.Optional[Trace]:
     tid_timeout = self._reassembler.transfer_id_timeout
     tr = self._reassembler.process_frame(timestamp, frame)
     if isinstance(tr, TransferReassembler.Error):
         return UDPErrorTrace(timestamp=timestamp, error=tr)
     if isinstance(tr, TransferFrom):
         meta = AlienTransferMetadata(tr.priority, tr.transfer_id, self._specifier)
         return TransferTrace(timestamp, AlienTransfer(meta, tr.fragmented_payload), tid_timeout)
     assert tr is None
     return None
Beispiel #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())
Beispiel #5
0
 def update(self, cap: Capture) -> typing.Optional[Trace]:
     if not isinstance(cap, CANCapture):
         return None
     parsed = cap.parse()
     if not parsed:
         return None
     ss, prio, frame = parsed
     if ss.source_node_id is not None:
         return self._get_session(ss).update(cap.timestamp, prio, frame)
     # Anonymous transfer -- no reconstruction needed, no session.
     return TransferTrace(
         cap.timestamp,
         AlienTransfer(AlienTransferMetadata(prio, frame.transfer_id, ss), [frame.padded_payload]),
         0.0,
     )
Beispiel #6
0
    def update(self, timestamp: Timestamp, priority: Priority, frame: UAVCANFrame) -> typing.Optional[Trace]:
        tid_timeout = self.transfer_id_timeout
        tr = self._reassembler.process_frame(timestamp, priority, frame, int(tid_timeout * 1e9))
        if tr is None:
            return None
        if isinstance(tr, TransferReassemblyErrorID):
            return CANErrorTrace(timestamp=timestamp, error=tr)

        assert isinstance(tr, TransferFrom)
        meta = AlienTransferMetadata(tr.priority, tr.transfer_id, self._specifier)
        out = TransferTrace(timestamp, AlienTransfer(meta, tr.fragmented_payload), tid_timeout)

        # Update the transfer interval for automatic transfer-ID timeout deduction.
        delta = float(tr.timestamp.monotonic) - self._last_transfer_monotonic
        delta = min(_AlienSession._MAX_INTERVAL, max(0.0, delta))
        self._interval = (self._interval + delta) * 0.5
        self._last_transfer_monotonic = float(tr.timestamp.monotonic)

        return out
Beispiel #7
0
    def update(self, timestamp: Timestamp,
               frame: SerialFrame) -> typing.Optional[Trace]:
        reasm = self._reassembler
        tid_timeout = reasm.transfer_id_timeout if reasm is not None else 0.0

        tr: typing.Union[TransferFrom, TransferReassembler.Error, None]
        if reasm is not None:
            tr = reasm.process_frame(timestamp, frame)
        else:
            tr = TransferReassembler.construct_anonymous_transfer(
                timestamp, frame)

        if isinstance(tr, TransferReassembler.Error):
            return SerialErrorTrace(timestamp=timestamp, error=tr)

        if isinstance(tr, TransferFrom):
            meta = AlienTransferMetadata(tr.priority, tr.transfer_id,
                                         self._specifier)
            return TransferTrace(timestamp,
                                 AlienTransfer(meta, tr.fragmented_payload),
                                 tid_timeout)

        assert tr is None
        return None
Beispiel #8
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
Beispiel #9
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)
Beispiel #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,
        )