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
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
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 _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 _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), ))
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
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)
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.
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, )