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
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)
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
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())
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, )
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
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
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
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)
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, )