def _unittest_transport_primitives() -> None: from pytest import raises from pyuavcan.transport import InputSessionSpecifier, OutputSessionSpecifier from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata with raises(ValueError): MessageDataSpecifier(-1) with raises(ValueError): MessageDataSpecifier(32768) with raises(ValueError): ServiceDataSpecifier(-1, ServiceDataSpecifier.Role.REQUEST) with raises(ValueError): InputSessionSpecifier(MessageDataSpecifier(123), -1) with raises(ValueError): OutputSessionSpecifier(ServiceDataSpecifier(100, ServiceDataSpecifier.Role.RESPONSE), None) with raises(ValueError): PayloadMetadata(-1, 0) with raises(ValueError): PayloadMetadata(2 ** 64, 0) with raises(ValueError): PayloadMetadata(0, -1)
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
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 _unittest_frame_compile_message() -> None: from pyuavcan.transport import Priority, MessageDataSpecifier, Timestamp f = SerialFrame(timestamp=Timestamp.now(), priority=Priority.HIGH, source_node_id=SerialFrame.FRAME_DELIMITER_BYTE, destination_node_id=SerialFrame.ESCAPE_PREFIX_BYTE, data_specifier=MessageDataSpecifier(12345), data_type_hash=0xdead_beef_bad_c0ffe, transfer_id=1234567890123456789, index=1234567, end_of_transfer=True, payload=memoryview(b'abcd\x9Eef\x8E')) buffer = bytearray(0 for _ in range(1000)) mv = f.compile_into(buffer) assert mv[0] == SerialFrame.FRAME_DELIMITER_BYTE assert mv[-1] == SerialFrame.FRAME_DELIMITER_BYTE segment = bytes(mv[1:-1]) assert SerialFrame.FRAME_DELIMITER_BYTE not in segment # Header validation assert segment[0] == _VERSION assert segment[1] == int(Priority.HIGH) assert segment[2] == SerialFrame.ESCAPE_PREFIX_BYTE assert (segment[3], segment[4]) == (SerialFrame.FRAME_DELIMITER_BYTE ^ 0xFF, 0) assert segment[5] == SerialFrame.ESCAPE_PREFIX_BYTE assert (segment[6], segment[7]) == (SerialFrame.ESCAPE_PREFIX_BYTE ^ 0xFF, 0) assert segment[8:10] == 12345 .to_bytes(2, 'little') assert segment[10:18] == 0xdead_beef_bad_c0ffe .to_bytes(8, 'little') assert segment[18:26] == 1234567890123456789 .to_bytes(8, 'little') assert segment[26:30] == (1234567 + 0x8000_0000).to_bytes(4, 'little') assert segment[30:34] == b'\x00' * 4 # Payload validation assert segment[34:38] == b'abcd' assert segment[38] == SerialFrame.ESCAPE_PREFIX_BYTE assert segment[39] == 0x9E ^ 0xFF assert segment[40:42] == b'ef' assert segment[42] == SerialFrame.ESCAPE_PREFIX_BYTE assert segment[43] == 0x8E ^ 0xFF # CRC validation header = SerialFrame.HEADER_STRUCT.pack(_VERSION, int(f.priority), f.source_node_id, f.destination_node_id, 12345, f.data_type_hash, f.transfer_id, f.index + 0x8000_0000) assert segment[44:] == pyuavcan.transport.commons.crc.CRC32C.new(header, f.payload).value_as_bytes
def _unittest_input_dispatch_table() -> None: import asyncio from pytest import raises from pyuavcan.transport import PayloadMetadata t = InputDispatchTable() assert len(list(t.items)) == 0 assert t.get(InputSessionSpecifier(MessageDataSpecifier(1234), None)) is None with raises(LookupError): t.remove(InputSessionSpecifier(MessageDataSpecifier(1234), 123)) a = CANInputSession( InputSessionSpecifier(MessageDataSpecifier(1234), None), PayloadMetadata(456, 789), asyncio.get_event_loop(), lambda: None) t.add(a) t.add(a) assert list(t.items) == [a] assert t.get(InputSessionSpecifier(MessageDataSpecifier(1234), None)) == a t.remove(InputSessionSpecifier(MessageDataSpecifier(1234), None)) assert len(list(t.items)) == 0
def _unittest_redundant_transport_reconfiguration() -> None: from pyuavcan.transport import OutputSessionSpecifier, MessageDataSpecifier, PayloadMetadata tr = RedundantTransport() tr.attach_inferior(LoopbackTransport(1234)) ses = tr.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(5555), None), PayloadMetadata(0)) assert ses tr.detach_inferior(tr.inferiors[0]) tr.attach_inferior(LoopbackTransport(1235)) # Different node-ID tr.detach_inferior(tr.inferiors[0]) tr.attach_inferior(LoopbackTransport(None, allow_anonymous_transfers=True)) # Anonymous with pytest.raises(pyuavcan.transport.OperationNotDefinedForAnonymousNodeError): tr.attach_inferior(LoopbackTransport(None, allow_anonymous_transfers=False)) assert len(tr.inferiors) == 1
async def _unittest_issue_120() -> None: from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer from pyuavcan.transport import Priority, Timestamp, OutputSessionSpecifier from .media.mock import MockMedia asyncio.get_running_loop().slow_callback_duration = 5.0 peers: typing.Set[MockMedia] = set() media = MockMedia(peers, 8, 10) tr = can.CANTransport(media, 42) assert tr.protocol_parameters.transfer_id_modulo == 32 feedback_collector = _FeedbackCollector() ses = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), PayloadMetadata(1024)) ses.enable_feedback(feedback_collector.give) for i in range(70): ts = Timestamp.now() assert await ses.send( Transfer( timestamp=ts, priority=Priority.SLOW, transfer_id=i, fragmented_payload=[_mem(str(i))] * 7, # Ensure both single- and multiframe ), tr.loop.time() + 1.0, ) await asyncio.sleep(0.1) fb = feedback_collector.take() assert fb.original_transfer_timestamp == ts num_frames = (10 * 1) + (60 * 3) # 10 single-frame, 60 multi-frame assert 70 == ses.sample_statistics().transfers assert num_frames == ses.sample_statistics().frames assert 0 == tr.sample_statistics().in_frames # loopback not included here assert 70 == tr.sample_statistics( ).in_frames_loopback # only first frame of each transfer assert num_frames == tr.sample_statistics().out_frames assert 70 == tr.sample_statistics( ).out_frames_loopback # only first frame of each transfer assert 0 == tr.sample_statistics().lost_loopback_frames
def _unittest_slow_input_dispatch_table_index() -> None: values: typing.Set[int] = set() for node_id in (*range(InputDispatchTable._NUM_NODE_IDS), None): for subj in range(InputDispatchTable._NUM_SUBJECTS): out = InputDispatchTable._compute_index( InputSessionSpecifier(MessageDataSpecifier(subj), node_id)) assert out not in values values.add(out) assert out < InputDispatchTable._TABLE_SIZE for serv in range(InputDispatchTable._NUM_SERVICES): for role in ServiceDataSpecifier.Role: out = InputDispatchTable._compute_index( InputSessionSpecifier(ServiceDataSpecifier(serv, role), node_id)) assert out not in values values.add(out) assert out < InputDispatchTable._TABLE_SIZE assert len(values) == InputDispatchTable._TABLE_SIZE
def _unittest_frame_compile_message() -> None: from pyuavcan.transport import Priority, MessageDataSpecifier f = SerialFrame( priority=Priority.HIGH, source_node_id=SerialFrame.FRAME_DELIMITER_BYTE, destination_node_id=SerialFrame.FRAME_DELIMITER_BYTE, data_specifier=MessageDataSpecifier(2345), transfer_id=1234567890123456789, index=1234567, end_of_transfer=True, payload=memoryview(b"abcd\x00ef\x00"), ) buffer = bytearray(0 for _ in range(1000)) mv = f.compile_into(buffer) assert mv[0] == SerialFrame.FRAME_DELIMITER_BYTE assert mv[-1] == SerialFrame.FRAME_DELIMITER_BYTE segment_cobs = bytes(mv[1:-1]) assert SerialFrame.FRAME_DELIMITER_BYTE not in segment_cobs segment = cobs.decode(segment_cobs) # Header validation assert segment[0] == _VERSION assert segment[1] == int(Priority.HIGH) assert (segment[2], segment[3]) == (SerialFrame.FRAME_DELIMITER_BYTE, 0) assert (segment[4], segment[5]) == (SerialFrame.FRAME_DELIMITER_BYTE, 0) assert segment[6:8] == 2345.to_bytes(2, "little") assert segment[8:16] == b"\x00" * 8 assert segment[16:24] == 1234567890123456789.to_bytes(8, "little") assert segment[24:28] == (1234567 + 0x8000_0000).to_bytes(4, "little") # Header CRC here # Payload validation assert segment[32:40] == b"abcd\x00ef\x00" assert segment[40:] == pyuavcan.transport.commons.crc.CRC32C.new( f.payload).value_as_bytes
def multicast_group_to_message_data_specifier( local_ip_address: IPAddress, multicast_group: IPAddress ) -> typing.Optional[MessageDataSpecifier]: """ The inverse of :func:`message_data_specifier_to_multicast_group`. The local IP address is needed to ensure that the multicast group belongs to the correct UAVCAN/UDP subnet. The return value is None if the multicast group is not valid per the current UAVCAN/UDP specification or if it belongs to a different UAVCAN/UDP subnet. >>> from ipaddress import ip_address >>> multicast_group_to_message_data_specifier(ip_address('127.42.11.22'), ip_address('239.42.1.200')) MessageDataSpecifier(subject_id=456) >>> multicast_group_to_message_data_specifier(ip_address('127.42.11.22'), ip_address('239.43.1.200')) # -> None >>> multicast_group_to_message_data_specifier(ip_address('127.42.11.22'), ip_address('239.42.255.200')) # -> None """ try: candidate = MessageDataSpecifier(int(multicast_group) & MULTICAST_GROUP_SUBJECT_ID_MASK) except ValueError: return None if message_data_specifier_to_multicast_group(local_ip_address, candidate) == multicast_group: return candidate return None
async def _unittest_redundant_transport(caplog: typing.Any) -> None: from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier from pyuavcan.transport import ProtocolParameters loop = asyncio.get_event_loop() loop.slow_callback_duration = 1.0 tr_a = RedundantTransport() tr_b = RedundantTransport(loop) assert tr_a.sample_statistics() == RedundantTransportStatistics([]) assert tr_a.inferiors == [] assert tr_a.local_node_id is None assert tr_a.loop is asyncio.get_event_loop() assert tr_a.local_node_id is None assert tr_a.protocol_parameters == ProtocolParameters( transfer_id_modulo=0, max_nodes=0, mtu=0, ) assert tr_a.descriptor == '<redundant></redundant>' # Empty, no inferiors. assert tr_a.input_sessions == [] assert tr_a.output_sessions == [] assert tr_a.loop == tr_b.loop # # Instantiate session objects. # meta = PayloadMetadata(10_240) pub_a = tr_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) sub_any_a = tr_a.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), None), meta) assert pub_a is tr_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) assert set(tr_a.input_sessions) == {sub_any_a} assert set(tr_a.output_sessions) == {pub_a} assert tr_a.sample_statistics() == RedundantTransportStatistics() pub_b = tr_b.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) sub_any_b = tr_b.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), None), meta) sub_sel_b = tr_b.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta) assert sub_sel_b is tr_b.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta) assert set(tr_b.input_sessions) == {sub_any_b, sub_sel_b} assert set(tr_b.output_sessions) == {pub_b} assert tr_b.sample_statistics() == RedundantTransportStatistics() # # Exchange test with no inferiors, expected to fail. # assert len(pub_a.inferiors) == 0 assert len(sub_any_a.inferiors) == 0 assert not await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=1, fragmented_payload=[memoryview(b'abc')]), monotonic_deadline=loop.time() + 1.0) assert not await sub_any_a.receive_until(loop.time() + 0.1) assert not await sub_any_b.receive_until(loop.time() + 0.1) assert tr_a.sample_statistics() == RedundantTransportStatistics() assert tr_b.sample_statistics() == RedundantTransportStatistics() # # Adding inferiors - loopback, transport A only. # with pytest.raises(InconsistentInferiorConfigurationError, match='(?i).*loop.*'): tr_a.attach_inferior( LoopbackTransport( 111, loop=asyncio.new_event_loop())) # Wrong event loop. assert len(pub_a.inferiors) == 0 assert len(sub_any_a.inferiors) == 0 lo_mono_0 = LoopbackTransport(111) lo_mono_1 = LoopbackTransport(111) tr_a.attach_inferior(lo_mono_0) assert len(pub_a.inferiors) == 1 assert len(sub_any_a.inferiors) == 1 with pytest.raises(ValueError): tr_a.detach_inferior(lo_mono_1) # Not a registered inferior (yet). tr_a.attach_inferior(lo_mono_1) assert len(pub_a.inferiors) == 2 assert len(sub_any_a.inferiors) == 2 with pytest.raises(ValueError): tr_a.attach_inferior(lo_mono_0) # Double-add not allowed. with pytest.raises(InconsistentInferiorConfigurationError, match='(?i).*node-id.*'): tr_a.attach_inferior(LoopbackTransport(None)) # Wrong node-ID. with pytest.raises(InconsistentInferiorConfigurationError, match='(?i).*node-id.*'): tr_a.attach_inferior(LoopbackTransport(1230)) # Wrong node-ID. assert tr_a.inferiors == [lo_mono_0, lo_mono_1] assert len(pub_a.inferiors) == 2 assert len(sub_any_a.inferiors) == 2 assert tr_a.sample_statistics() == RedundantTransportStatistics(inferiors=[ lo_mono_0.sample_statistics(), lo_mono_1.sample_statistics(), ]) assert tr_a.local_node_id == 111 assert tr_a.descriptor == '<redundant><loopback/><loopback/></redundant>' assert await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=2, fragmented_payload=[memoryview(b'def')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_a.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'def')] assert rx.transfer_id == 2 assert not await sub_any_b.receive_until(loop.time() + 0.1) # # Incapacitate one inferior, ensure things are still OK. # with caplog.at_level(logging.CRITICAL, logger=pyuavcan.transport.redundant.__name__): for s in lo_mono_0.output_sessions: s.exception = RuntimeError('INTENDED EXCEPTION') assert await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=3, fragmented_payload=[memoryview(b'qwe')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_a.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'qwe')] assert rx.transfer_id == 3 # # Remove old loopback transports. Configure new ones with cyclic TID. # lo_cyc_0 = LoopbackTransport(111) lo_cyc_1 = LoopbackTransport(111) cyc_proto_params = ProtocolParameters( transfer_id_modulo=32, # Like CAN max_nodes=128, # Like CAN mtu=63, # Like CAN ) lo_cyc_0.protocol_parameters = cyc_proto_params lo_cyc_1.protocol_parameters = cyc_proto_params assert lo_cyc_0.protocol_parameters == lo_cyc_1.protocol_parameters == cyc_proto_params assert tr_a.protocol_parameters.transfer_id_modulo >= 2**56 with pytest.raises(InconsistentInferiorConfigurationError, match='(?i).*transfer-id.*'): tr_a.attach_inferior(lo_cyc_0) # Transfer-ID modulo mismatch tr_a.detach_inferior(lo_mono_0) tr_a.detach_inferior(lo_mono_1) del lo_mono_0 # Prevent accidental reuse. del lo_mono_1 assert tr_a.inferiors == [] # All removed, okay. assert pub_a.inferiors == [] assert sub_any_a.inferiors == [] assert tr_a.local_node_id is None # Back to the roots assert tr_a.descriptor == '<redundant></redundant>' # Yes yes # Now we can add our cyclic transports safely. tr_a.attach_inferior(lo_cyc_0) assert tr_a.protocol_parameters.transfer_id_modulo == 32 tr_a.attach_inferior(lo_cyc_1) assert tr_a.protocol_parameters == cyc_proto_params, 'Protocol parameter mismatch' assert tr_a.local_node_id == 111 assert tr_a.descriptor == '<redundant><loopback/><loopback/></redundant>' # Exchange test. assert await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=4, fragmented_payload=[memoryview(b'rty')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_a.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'rty')] assert rx.transfer_id == 4 # # Real heterogeneous transport test. # tr_a.detach_inferior(lo_cyc_0) tr_a.detach_inferior(lo_cyc_1) del lo_cyc_0 # Prevent accidental reuse. del lo_cyc_1 udp_a = UDPTransport('127.0.0.111/8') udp_b = UDPTransport('127.0.0.222/8') serial_a = SerialTransport(SERIAL_URI, 111) serial_b = SerialTransport(SERIAL_URI, 222, mtu=2048) # Heterogeneous. tr_a.attach_inferior(udp_a) tr_a.attach_inferior(serial_a) tr_b.attach_inferior(udp_b) tr_b.attach_inferior(serial_b) print('tr_a.descriptor', tr_a.descriptor) print('tr_b.descriptor', tr_b.descriptor) assert tr_a.protocol_parameters == ProtocolParameters( transfer_id_modulo=2**64, max_nodes=4096, mtu=1024, ) assert tr_a.local_node_id == 111 assert tr_a.descriptor == f'<redundant>{udp_a.descriptor}{serial_a.descriptor}</redundant>' assert tr_b.protocol_parameters == ProtocolParameters( transfer_id_modulo=2**64, max_nodes=4096, mtu=1024, ) assert tr_b.local_node_id == 222 assert tr_b.descriptor == f'<redundant>{udp_b.descriptor}{serial_b.descriptor}</redundant>' assert await pub_a.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=5, fragmented_payload=[memoryview(b'uio')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_b.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'uio')] assert rx.transfer_id == 5 assert not await sub_any_a.receive_until(loop.time() + 0.1) assert not await sub_any_b.receive_until(loop.time() + 0.1) assert not await sub_sel_b.receive_until(loop.time() + 0.1) # # Construct new session with the transports configured. # pub_a_new = tr_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), 222), meta) assert pub_a_new is tr_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), 222), meta) assert set(tr_a.output_sessions) == {pub_a, pub_a_new} assert await pub_a_new.send_until(Transfer( timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=6, fragmented_payload=[memoryview(b'asd')]), monotonic_deadline=loop.time() + 1.0) rx = await sub_any_b.receive_until(loop.time() + 1.0) assert rx is not None assert rx.fragmented_payload == [memoryview(b'asd')] assert rx.transfer_id == 6 # # Termination. # tr_a.close() tr_a.close() # Idempotency tr_b.close() tr_b.close() # Idempotency with pytest.raises(pyuavcan.transport.ResourceClosedError ): # Make sure the inferiors are closed. udp_a.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) with pytest.raises(pyuavcan.transport.ResourceClosedError ): # Make sure the inferiors are closed. serial_b.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta) with pytest.raises(pyuavcan.transport.ResourceClosedError ): # Make sure the sessions are closed. await pub_a.send_until(Transfer(timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=100, fragmented_payload=[]), monotonic_deadline=loop.time() + 1.0) await asyncio.sleep( 1 ) # Let all pending tasks finalize properly to avoid stack traces in the output.
async def _unittest_can_transport_non_anon() -> None: from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom from pyuavcan.transport import UnsupportedSessionConfigurationError, Priority, SessionStatistics, Timestamp from pyuavcan.transport import ResourceClosedError, InputSessionSpecifier, OutputSessionSpecifier # noinspection PyProtectedMember from pyuavcan.transport.can._identifier import MessageCANID, ServiceCANID # noinspection PyProtectedMember from pyuavcan.transport.can._frame import UAVCANFrame from .media.mock import MockMedia, FrameCollector peers: typing.Set[MockMedia] = set() media = MockMedia(peers, 64, 10) media2 = MockMedia(peers, 64, 3) peeper = MockMedia(peers, 64, 10) assert len(peers) == 3 tr = can.CANTransport(media, 5) tr2 = can.CANTransport(media2, 123) assert tr.protocol_parameters == pyuavcan.transport.ProtocolParameters( transfer_id_modulo=32, max_nodes=128, mtu=63) assert tr.local_node_id == 5 assert tr.protocol_parameters == tr2.protocol_parameters assert media.automatic_retransmission_enabled assert media2.automatic_retransmission_enabled # # Instantiate session objects # meta = PayloadMetadata(0x_bad_c0ffee_0dd_f00d, 10000) with pytest.raises(Exception): # Can't broadcast service calls tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(123, ServiceDataSpecifier.Role.RESPONSE), None), meta) with pytest.raises( UnsupportedSessionConfigurationError): # Can't unicast messages tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(1234), 123), meta) broadcaster = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert broadcaster is tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) subscriber_promiscuous = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), None), meta) assert subscriber_promiscuous is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), None), meta) subscriber_selective = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), 123), meta) server_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), None), meta) server_responder = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) client_requester = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 123), meta) client_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) assert set(tr.input_sessions) == { subscriber_promiscuous, subscriber_selective, server_listener, client_listener } assert set(tr.output_sessions) == { broadcaster, server_responder, client_requester } # # Basic exchange test, no one is listening # media2.configure_acceptance_filters( [can.media.FilterConfiguration.new_promiscuous()]) peeper.configure_acceptance_filters( [can.media.FilterConfiguration.new_promiscuous()]) collector = FrameCollector() peeper.start(collector.give, False) assert tr.sample_statistics() == can.CANTransportStatistics() assert tr2.sample_statistics() == can.CANTransportStatistics() ts = Timestamp.now() def validate_timestamp(timestamp: Timestamp) -> None: assert ts.monotonic_ns <= timestamp.monotonic_ns <= time.monotonic_ns() assert ts.system_ns <= timestamp.system_ns <= time.time_ns() assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=32 + 11, # Modulus 11 fragmented_payload=[_mem('abc'), _mem('def')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=6) assert tr.sample_statistics() == can.CANTransportStatistics(out_frames=1) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=1, in_frames_uavcan=1) assert tr.sample_statistics( ).media_acceptance_filtering_efficiency == pytest.approx(1) assert tr2.sample_statistics( ).media_acceptance_filtering_efficiency == pytest.approx(0) assert tr.sample_statistics().lost_loopback_frames == 0 assert tr2.sample_statistics().lost_loopback_frames == 0 assert collector.pop().is_same_manifestation( UAVCANFrame( identifier=MessageCANID(Priority.IMMEDIATE, 5, 12345).compile( [_mem('abcdef')]), # payload fragments joined padded_payload=_mem('abcdef'), transfer_id=11, start_of_transfer=True, end_of_transfer=True, toggle_bit=True, loopback=False).compile()) assert collector.empty # # Broadcast exchange with input dispatch test # selective_m12345_5 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 5), meta) selective_m12345_9 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 9), meta) promiscuous_m12345 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=32 + 11, # Modulus 11 fragmented_payload=[_mem('abc'), _mem('def')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=2, frames=2, payload_bytes=12) assert tr.sample_statistics() == can.CANTransportStatistics(out_frames=2) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=2, in_frames_uavcan=2, in_frames_uavcan_accepted=1) received = await promiscuous_m12345.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 11 assert received.source_node_id == 5 assert received.priority == Priority.IMMEDIATE validate_timestamp(received.timestamp) assert received.fragmented_payload == [_mem('abcdef')] assert selective_m12345_5.sample_statistics() == SessionStatistics( ) # Nothing assert selective_m12345_9.sample_statistics() == SessionStatistics( ) # Nothing assert promiscuous_m12345.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=6) assert media.automatic_retransmission_enabled assert media2.automatic_retransmission_enabled feedback_collector = _FeedbackCollector() broadcaster.enable_feedback(feedback_collector.give) assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.SLOW, transfer_id=2, fragmented_payload=[_mem('qwe'), _mem('rty')] * 50 # Lots of data here, very multiframe ), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=3, frames=7, payload_bytes=312) broadcaster.disable_feedback() assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=7, out_frames_loopback=1, in_frames_loopback=1) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=7, in_frames_uavcan=7, in_frames_uavcan_accepted=6) fb = feedback_collector.take() assert fb.original_transfer_timestamp == ts validate_timestamp(fb.first_frame_transmission_timestamp) received = await promiscuous_m12345.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 2 assert received.source_node_id == 5 assert received.priority == Priority.SLOW validate_timestamp(received.timestamp) assert b''.join( received.fragmented_payload ) == b'qwerty' * 50 + b'\x00' * 13 # The 0x00 at the end is padding assert await broadcaster.send_until( Transfer(timestamp=ts, priority=Priority.OPTIONAL, transfer_id=3, fragmented_payload=[_mem('qwe'), _mem('rty')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=4, frames=8, payload_bytes=318) received = await promiscuous_m12345.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 3 assert received.source_node_id == 5 assert received.priority == Priority.OPTIONAL validate_timestamp(received.timestamp) assert list(received.fragmented_payload) == [_mem('qwerty')] assert promiscuous_m12345.sample_statistics() == SessionStatistics( transfers=3, frames=7, payload_bytes=325) assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=8, out_frames_loopback=1, in_frames_loopback=1) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=8, in_frames_uavcan=8, in_frames_uavcan_accepted=7) broadcaster.close() with pytest.raises(ResourceClosedError): assert await broadcaster.send_until( Transfer(timestamp=ts, priority=Priority.LOW, transfer_id=4, fragmented_payload=[]), tr.loop.time() + 1.0) broadcaster.close() # Does nothing # Final checks for the broadcaster - make sure nothing is left in the queue assert (await promiscuous_m12345.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None # The selective listener was not supposed to pick up anything because it's selective for node 9, not 5 assert (await selective_m12345_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None # Now, there are a bunch of items awaiting in the selective input for node 5, collect them and check the stats assert selective_m12345_5.source_node_id == 5 received = await selective_m12345_5.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 11 assert received.source_node_id == 5 assert received.priority == Priority.IMMEDIATE validate_timestamp(received.timestamp) assert received.fragmented_payload == [_mem('abcdef')] received = await selective_m12345_5.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 2 assert received.source_node_id == 5 assert received.priority == Priority.SLOW validate_timestamp(received.timestamp) assert b''.join( received.fragmented_payload ) == b'qwerty' * 50 + b'\x00' * 13 # The 0x00 at the end is padding received = await selective_m12345_5.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 3 assert received.source_node_id == 5 assert received.priority == Priority.OPTIONAL validate_timestamp(received.timestamp) assert list(received.fragmented_payload) == [_mem('qwerty')] assert selective_m12345_5.sample_statistics( ) == promiscuous_m12345.sample_statistics() # # Unicast exchange test # selective_server_s333_5 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 5), meta) selective_server_s333_9 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 9), meta) promiscuous_server_s333 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), None), meta) selective_client_s333_5 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 5), meta) selective_client_s333_9 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 9), meta) promiscuous_client_s333 = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), None), meta) assert await client_requester.send_until( Transfer(timestamp=ts, priority=Priority.FAST, transfer_id=11, fragmented_payload=[]), tr.loop.time() + 1.0) assert client_requester.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=0) received = await selective_server_s333_5.receive_until( tr.loop.time() + 1.0) # Same thing here assert received is not None assert received.transfer_id == 11 assert received.priority == Priority.FAST validate_timestamp(received.timestamp) assert list(map(bytes, received.fragmented_payload)) == [b''] assert (await selective_server_s333_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None received = await promiscuous_server_s333.receive_until( tr.loop.time() + 1.0) # Same thing here assert received is not None assert received.transfer_id == 11 assert received.priority == Priority.FAST validate_timestamp(received.timestamp) assert list(map(bytes, received.fragmented_payload)) == [b''] assert selective_server_s333_5.sample_statistics() == SessionStatistics( transfers=1, frames=1) assert selective_server_s333_9.sample_statistics() == SessionStatistics() assert promiscuous_server_s333.sample_statistics() == SessionStatistics( transfers=1, frames=1) assert (await selective_client_s333_5.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert (await selective_client_s333_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert (await promiscuous_client_s333.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert selective_client_s333_5.sample_statistics() == SessionStatistics() assert selective_client_s333_9.sample_statistics() == SessionStatistics() assert promiscuous_client_s333.sample_statistics() == SessionStatistics() client_requester.enable_feedback( feedback_collector.give) # FEEDBACK ENABLED HERE # Will fail with an error; make sure it's counted properly. The feedback registry entry will remain pending! media.raise_on_send_once(RuntimeError('Induced failure')) with pytest.raises(RuntimeError, match='Induced failure'): assert await client_requester.send_until( Transfer(timestamp=ts, priority=Priority.FAST, transfer_id=12, fragmented_payload=[]), tr.loop.time() + 1.0) assert client_requester.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=0, errors=1) # Some malformed feedback frames which will be ignored media.inject_received([ UAVCANFrame( identifier=ServiceCANID(priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=333, request_not_response=True).compile( [_mem('Ignored')]), padded_payload=_mem('Ignored'), start_of_transfer=False, # Ignored because not start-of-frame end_of_transfer=False, toggle_bit=True, transfer_id=12, loopback=True).compile() ]) media.inject_received([ UAVCANFrame( identifier=ServiceCANID(priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=333, request_not_response=True).compile( [_mem('Ignored')]), padded_payload=_mem('Ignored'), start_of_transfer=True, end_of_transfer=False, toggle_bit=True, transfer_id= 9, # Ignored because there is no such transfer-ID in the registry loopback=True).compile() ]) # Now, this transmission will succeed, but a pending loopback registry entry will be overwritten, which will be # reflected in the error counter. assert await client_requester.send_until( Transfer( timestamp=ts, priority=Priority.FAST, transfer_id=12, fragmented_payload=[ _mem( 'Until philosophers are kings, or the kings and princes of this world have the spirit and power of ' 'philosophy, and political greatness and wisdom meet in one, and those commoner natures who pursue ' 'either to the exclusion of the other are compelled to stand aside, cities will never have rest from ' 'their evils '), _mem('- no, nor the human race, as I believe - '), _mem( 'and then only will this our State have a possibility of life and behold the light of day.' ), ]), tr.loop.time() + 1.0) client_requester.disable_feedback() assert client_requester.sample_statistics() == SessionStatistics( transfers=2, frames=8, payload_bytes=438, errors=2) # The feedback is disabled, but we will send a valid loopback frame anyway to make sure it is silently ignored media.inject_received([ UAVCANFrame(identifier=ServiceCANID(priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=333, request_not_response=True).compile( [_mem('Ignored')]), padded_payload=_mem('Ignored'), start_of_transfer=True, end_of_transfer=False, toggle_bit=True, transfer_id=12, loopback=True).compile() ]) client_requester.close() with pytest.raises(ResourceClosedError): assert await client_requester.send_until( Transfer(timestamp=ts, priority=Priority.LOW, transfer_id=4, fragmented_payload=[]), tr.loop.time() + 1.0) fb = feedback_collector.take() assert fb.original_transfer_timestamp == ts validate_timestamp(fb.first_frame_transmission_timestamp) received = await promiscuous_server_s333.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.source_node_id == 5 assert received.transfer_id == 12 assert received.priority == Priority.FAST validate_timestamp(received.timestamp) assert len(received.fragmented_payload) == 7 # Equals the number of frames assert sum(map( len, received.fragmented_payload)) == 438 + 1 # Padding also included assert b'Until philosophers are kings' in bytes( received.fragmented_payload[0]) assert b'behold the light of day.' in bytes( received.fragmented_payload[-1]) received = await selective_server_s333_5.receive_until( tr.loop.time() + 1.0) # Same thing here assert received is not None assert received.transfer_id == 12 assert received.priority == Priority.FAST validate_timestamp(received.timestamp) assert len(received.fragmented_payload) == 7 # Equals the number of frames assert sum(map( len, received.fragmented_payload)) == 438 + 1 # Padding also included assert b'Until philosophers are kings' in bytes( received.fragmented_payload[0]) assert b'behold the light of day.' in bytes( received.fragmented_payload[-1]) # Nothing is received - non-matching node ID selector assert (await selective_server_s333_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None # Nothing is received - non-matching role (not server) assert (await selective_client_s333_5.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert (await selective_client_s333_9.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert (await promiscuous_client_s333.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert selective_client_s333_5.sample_statistics() == SessionStatistics() assert selective_client_s333_9.sample_statistics() == SessionStatistics() assert promiscuous_client_s333.sample_statistics() == SessionStatistics() # Final transport stats check; additional loopback frames are due to our manual tests above assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=16, out_frames_loopback=2, in_frames_loopback=5) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=16, in_frames_uavcan=16, in_frames_uavcan_accepted=15) # # Drop non-UAVCAN frames silently # media.inject_received([ can.media.DataFrame( identifier=ServiceCANID(priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=333, request_not_response=True).compile( [_mem('')]), data=bytearray( b'' ), # The CAN ID is valid for UAVCAN, but the payload is not - no tail byte format=can.media.FrameFormat.EXTENDED, loopback=False) ]) media.inject_received([ can.media.DataFrame( identifier=0, # The CAN ID is not valid for UAVCAN data=bytearray(b'123'), format=can.media.FrameFormat.BASE, loopback=False) ]) media.inject_received([ UAVCANFrame( identifier=ServiceCANID( priority=Priority.FAST, source_node_id=5, destination_node_id=123, service_id=444, # No such service request_not_response=True).compile([_mem('Ignored')]), padded_payload=_mem('Ignored'), start_of_transfer=True, end_of_transfer=False, toggle_bit=True, transfer_id=12, loopback=True).compile() ]) assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=16, in_frames=2, out_frames_loopback=2, in_frames_loopback=6) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=16, in_frames_uavcan=16, in_frames_uavcan_accepted=15) # # Reception logic test. # pub_m2222 = tr2.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2222), None), meta) # Transfer ID timeout configuration - one of them will be configured very short for testing purposes subscriber_promiscuous.transfer_id_timeout = 1e-9 # Very low, basically zero timeout with pytest.raises(ValueError): subscriber_promiscuous.transfer_id_timeout = -1 with pytest.raises(ValueError): subscriber_promiscuous.transfer_id_timeout = float('nan') assert subscriber_promiscuous.transfer_id_timeout == pytest.approx(1e-9) subscriber_selective.transfer_id_timeout = 1.0 with pytest.raises(ValueError): subscriber_selective.transfer_id_timeout = -1 with pytest.raises(ValueError): subscriber_selective.transfer_id_timeout = float('nan') assert subscriber_selective.transfer_id_timeout == pytest.approx(1.0) # Queue capacity configuration assert subscriber_selective.frame_queue_capacity is None # Unlimited by default subscriber_selective.frame_queue_capacity = 2 with pytest.raises(ValueError): subscriber_selective.frame_queue_capacity = 0 assert subscriber_selective.frame_queue_capacity == 2 assert await pub_m2222.send_until( Transfer( timestamp=ts, priority=Priority.EXCEPTIONAL, transfer_id=7, fragmented_payload=[ _mem('Finally, from so little sleeping and so much reading, '), _mem( 'his brain dried up and he went completely out of his mind.' ), # Two frames. ]), tr.loop.time() + 1.0) assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=16, in_frames=4, in_frames_uavcan=2, in_frames_uavcan_accepted=2, out_frames_loopback=2, in_frames_loopback=6) assert tr2.sample_statistics() == can.CANTransportStatistics( out_frames=2, in_frames=16, in_frames_uavcan=16, in_frames_uavcan_accepted=15) received = await subscriber_promiscuous.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.source_node_id == 123 assert received.priority == Priority.EXCEPTIONAL assert received.transfer_id == 7 validate_timestamp(received.timestamp) assert bytes(received.fragmented_payload[0]).startswith(b'Finally') assert bytes(received.fragmented_payload[-1]).rstrip(b'\x00').endswith( b'out of his mind.') received = await subscriber_selective.receive_until(tr.loop.time() + 1.0) assert received is not None assert received.priority == Priority.EXCEPTIONAL assert received.transfer_id == 7 validate_timestamp(received.timestamp) assert bytes(received.fragmented_payload[0]).startswith(b'Finally') assert bytes(received.fragmented_payload[-1]).rstrip(b'\x00').endswith( b'out of his mind.') assert subscriber_selective.sample_statistics( ) == subscriber_promiscuous.sample_statistics() assert subscriber_promiscuous.sample_statistics() == SessionStatistics( transfers=1, frames=2, payload_bytes=124) # Includes padding! assert await pub_m2222.send_until( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id= 7, # Same transfer ID, will be accepted only by the instance with low TID timeout fragmented_payload=[]), tr.loop.time() + 1.0) assert tr.sample_statistics() == can.CANTransportStatistics( out_frames=16, in_frames=5, in_frames_uavcan=3, in_frames_uavcan_accepted=3, out_frames_loopback=2, in_frames_loopback=6) assert tr2.sample_statistics() == can.CANTransportStatistics( out_frames=3, in_frames=16, in_frames_uavcan=16, in_frames_uavcan_accepted=15) received = await subscriber_promiscuous.receive_until(tr.loop.time() + 10.0) assert received is not None assert isinstance(received, TransferFrom) assert received.source_node_id == 123 assert received.priority == Priority.NOMINAL assert received.transfer_id == 7 validate_timestamp(received.timestamp) assert b''.join(received.fragmented_payload) == b'' assert subscriber_promiscuous.sample_statistics() == SessionStatistics( transfers=2, frames=3, payload_bytes=124) # Discarded because of the same transfer ID assert (await subscriber_selective.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert subscriber_selective.sample_statistics() == SessionStatistics( transfers=1, frames=3, payload_bytes=124, errors=1 # Error due to the repeated transfer ID ) assert await pub_m2222.send_until( Transfer( timestamp=ts, priority=Priority.HIGH, transfer_id=8, fragmented_payload=[ _mem('a' * 63), _mem('b' * 63), _mem('c' * 63), _mem( 'd' * 62 ), # Tricky case - one of the CRC bytes spills over into the fifth frame ]), tr.loop.time() + 1.0) # The promiscuous one is able to receive the transfer since its queue is large enough received = await subscriber_promiscuous.receive_until(tr.loop.time() + 1.0) assert received is not None assert received.priority == Priority.HIGH assert received.transfer_id == 8 validate_timestamp(received.timestamp) assert list(map(bytes, received.fragmented_payload)) == [ b'a' * 63, b'b' * 63, b'c' * 63, b'd' * 62, ] assert subscriber_promiscuous.sample_statistics() == SessionStatistics( transfers=3, frames=8, payload_bytes=375) # The selective one is unable to do so since its RX queue is too small; it is reflected in the error counter assert (await subscriber_selective.receive_until(tr.loop.time() + _RX_TIMEOUT)) is None assert subscriber_selective.sample_statistics() == SessionStatistics( transfers=1, frames=5, payload_bytes=124, errors=1, drops=3) # Overruns! # # Finalization. # print('str(CANTransport):', tr) print('str(CANTransport):', tr2) client_listener.close() server_listener.close() subscriber_promiscuous.close() subscriber_selective.close() tr.close() tr2.close() # Double-close has no effect: client_listener.close() server_listener.close() subscriber_promiscuous.close() subscriber_selective.close() tr.close() tr2.close()
async def _unittest_can_transport_anon() -> None: from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom from pyuavcan.transport import UnsupportedSessionConfigurationError, Priority, SessionStatistics, Timestamp from pyuavcan.transport import OperationNotDefinedForAnonymousNodeError from pyuavcan.transport import InputSessionSpecifier, OutputSessionSpecifier # noinspection PyProtectedMember from pyuavcan.transport.can._identifier import MessageCANID # noinspection PyProtectedMember from pyuavcan.transport.can._frame import UAVCANFrame from .media.mock import MockMedia, FrameCollector with pytest.raises(pyuavcan.transport.InvalidTransportConfigurationError): can.CANTransport(MockMedia(set(), 64, 0), None) with pytest.raises(pyuavcan.transport.InvalidTransportConfigurationError): can.CANTransport(MockMedia(set(), 7, 16), None) peers: typing.Set[MockMedia] = set() media = MockMedia(peers, 64, 10) media2 = MockMedia(peers, 64, 3) peeper = MockMedia(peers, 64, 10) assert len(peers) == 3 tr = can.CANTransport(media, None) tr2 = can.CANTransport(media2, None) assert tr.protocol_parameters == pyuavcan.transport.ProtocolParameters( transfer_id_modulo=32, max_nodes=128, mtu=63) assert tr.local_node_id is None assert tr.protocol_parameters == tr2.protocol_parameters assert not media.automatic_retransmission_enabled assert not media2.automatic_retransmission_enabled assert tr.descriptor == f'<can><mock mtu="64">mock@{id(peers):08x}</mock></can>' # # Instantiate session objects # meta = PayloadMetadata(0x_bad_c0ffee_0dd_f00d, 10000) with pytest.raises(Exception): # Can't broadcast service calls tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(123, ServiceDataSpecifier.Role.RESPONSE), None), meta) with pytest.raises( UnsupportedSessionConfigurationError): # Can't unicast messages tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(1234), 123), meta) broadcaster = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert broadcaster is tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) subscriber_promiscuous = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), None), meta) assert subscriber_promiscuous is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), None), meta) subscriber_selective = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), 123), meta) assert subscriber_selective is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(2222), 123), meta) server_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), None), meta) assert server_listener is tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), None), meta) server_responder = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) assert server_responder is tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) client_requester = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 123), meta) assert client_requester is tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 123), meta) client_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) assert client_listener is tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE), 123), meta) assert broadcaster.destination_node_id is None assert subscriber_promiscuous.source_node_id is None assert subscriber_selective.source_node_id == 123 assert server_listener.source_node_id is None assert client_listener.source_node_id == 123 base_ts = time.process_time() inputs = tr.input_sessions print( f'INPUTS (sampled in {time.process_time() - base_ts:.3f}s): {inputs}') assert set(inputs) == { subscriber_promiscuous, subscriber_selective, server_listener, client_listener } del inputs print('OUTPUTS:', tr.output_sessions) assert set(tr.output_sessions) == { broadcaster, server_responder, client_requester } # # Basic exchange test, no one is listening # media2.configure_acceptance_filters( [can.media.FilterConfiguration.new_promiscuous()]) peeper.configure_acceptance_filters( [can.media.FilterConfiguration.new_promiscuous()]) collector = FrameCollector() peeper.start(collector.give, False) assert tr.sample_statistics() == can.CANTransportStatistics() assert tr2.sample_statistics() == can.CANTransportStatistics() ts = Timestamp.now() def validate_timestamp(timestamp: Timestamp) -> None: assert ts.monotonic_ns <= timestamp.monotonic_ns <= time.monotonic_ns() assert ts.system_ns <= timestamp.system_ns <= time.time_ns() assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=32 + 11, # Modulus 11 fragmented_payload=[_mem('abc'), _mem('def')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=6) assert tr.sample_statistics() == can.CANTransportStatistics(out_frames=1) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=1, in_frames_uavcan=1) assert tr.sample_statistics( ).media_acceptance_filtering_efficiency == pytest.approx(1) assert tr2.sample_statistics( ).media_acceptance_filtering_efficiency == pytest.approx(0) assert tr.sample_statistics().lost_loopback_frames == 0 assert tr2.sample_statistics().lost_loopback_frames == 0 assert collector.pop().is_same_manifestation( UAVCANFrame( identifier=MessageCANID(Priority.IMMEDIATE, None, 12345).compile( [_mem('abcdef')]), # payload fragments joined padded_payload=_mem('abcdef'), transfer_id=11, start_of_transfer=True, end_of_transfer=True, toggle_bit=True, loopback=False).compile()) assert collector.empty # Can't send anonymous service transfers with pytest.raises(OperationNotDefinedForAnonymousNodeError): assert await client_requester.send_until( Transfer(timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=0, fragmented_payload=[]), tr.loop.time() + 1.0, ) assert client_requester.sample_statistics() == SessionStatistics( ) # Not incremented! # Can't send multiframe anonymous messages with pytest.raises(OperationNotDefinedForAnonymousNodeError): assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.SLOW, transfer_id=2, fragmented_payload=[_mem('qwe'), _mem('rty')] * 50 # Lots of data here, very multiframe ), tr.loop.time() + 1.0) # # Broadcast exchange with input dispatch test # selective_m12345_5 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 5), meta) selective_m12345_9 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 9), meta) promiscuous_m12345 = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert await broadcaster.send_until( Transfer( timestamp=ts, priority=Priority.IMMEDIATE, transfer_id=32 + 11, # Modulus 11 fragmented_payload=[_mem('abc'), _mem('def')]), tr.loop.time() + 1.0) assert broadcaster.sample_statistics() == SessionStatistics( transfers=2, frames=2, payload_bytes=12) assert tr.sample_statistics() == can.CANTransportStatistics(out_frames=2) assert tr2.sample_statistics() == can.CANTransportStatistics( in_frames=2, in_frames_uavcan=2, in_frames_uavcan_accepted=1) received = await promiscuous_m12345.receive_until(tr.loop.time() + 1.0) assert received is not None assert isinstance(received, TransferFrom) assert received.transfer_id == 11 assert received.source_node_id is None # The sender is anonymous assert received.priority == Priority.IMMEDIATE validate_timestamp(received.timestamp) assert received.fragmented_payload == [_mem('abcdef')] assert selective_m12345_5.sample_statistics() == SessionStatistics( ) # Nothing assert selective_m12345_9.sample_statistics() == SessionStatistics( ) # Nothing assert promiscuous_m12345.sample_statistics() == SessionStatistics( transfers=1, frames=1, payload_bytes=6) assert not media.automatic_retransmission_enabled assert not media2.automatic_retransmission_enabled # # Finalization. # print('str(CANTransport):', tr) print('str(CANTransport):', tr2) client_listener.close() server_listener.close() subscriber_promiscuous.close() subscriber_selective.close() tr.close() tr2.close() # Double-close has no effect: client_listener.close() server_listener.close() subscriber_promiscuous.close() subscriber_selective.close() tr.close() tr2.close()
def _unittest_frame_check() -> None: from pytest import raises from pyuavcan.transport import Priority, MessageDataSpecifier, ServiceDataSpecifier, Timestamp f = SerialFrame(timestamp=Timestamp.now(), priority=Priority.HIGH, source_node_id=123, destination_node_id=456, data_specifier=MessageDataSpecifier(12345), data_type_hash=0xdead_beef_bad_c0ffe, transfer_id=1234567890123456789, index=1234567, end_of_transfer=False, payload=memoryview(b'abcdef')) del f with raises(ValueError): SerialFrame(timestamp=Timestamp.now(), priority=Priority.HIGH, source_node_id=123456, destination_node_id=456, data_specifier=MessageDataSpecifier(12345), data_type_hash=0xdead_beef_bad_c0ffe, transfer_id=1234567890123456789, index=1234567, end_of_transfer=False, payload=memoryview(b'abcdef')) with raises(ValueError): SerialFrame(timestamp=Timestamp.now(), priority=Priority.HIGH, source_node_id=123, destination_node_id=123456, data_specifier=MessageDataSpecifier(12345), data_type_hash=0xdead_beef_bad_c0ffe, transfer_id=1234567890123456789, index=1234567, end_of_transfer=False, payload=memoryview(b'abcdef')) with raises(ValueError): SerialFrame(timestamp=Timestamp.now(), priority=Priority.HIGH, source_node_id=None, destination_node_id=456, data_specifier=ServiceDataSpecifier(123, ServiceDataSpecifier.Role.REQUEST), data_type_hash=0xdead_beef_bad_c0ffe, transfer_id=1234567890123456789, index=1234567, end_of_transfer=False, payload=memoryview(b'abcdef')) with raises(ValueError): SerialFrame(timestamp=Timestamp.now(), priority=Priority.HIGH, source_node_id=None, destination_node_id=None, data_specifier=MessageDataSpecifier(12345), data_type_hash=2 ** 64, transfer_id=1234567890123456789, index=1234567, end_of_transfer=False, payload=memoryview(b'abcdef')) with raises(ValueError): SerialFrame(timestamp=Timestamp.now(), priority=Priority.HIGH, source_node_id=None, destination_node_id=None, data_specifier=MessageDataSpecifier(12345), data_type_hash=0xdead_beef_bad_c0ffe, transfer_id=-1, index=1234567, end_of_transfer=False, payload=memoryview(b'abcdef')) with raises(ValueError): SerialFrame(timestamp=Timestamp.now(), priority=Priority.HIGH, source_node_id=None, destination_node_id=None, data_specifier=MessageDataSpecifier(12345), data_type_hash=0xdead_beef_bad_c0ffe, transfer_id=0, index=-1, end_of_transfer=False, payload=memoryview(b'abcdef'))
def _unittest_frame_parse() -> None: from pyuavcan.transport import Priority, MessageDataSpecifier, ServiceDataSpecifier ts = pyuavcan.transport.Timestamp.now() def get_crc(*blocks: typing.Union[bytes, memoryview]) -> bytes: return pyuavcan.transport.commons.crc.CRC32C.new(*blocks).value_as_bytes # Valid message with payload header = bytes([ _VERSION, int(Priority.LOW), 0x7B, 0x00, # Source NID 123 0xC8, 0x01, # Destination NID 456 0xE1, 0x10, # Data specifier 4321 0x0D, 0xF0, 0xDD, 0xE0, 0xFE, 0x0F, 0xDC, 0xBA, # Data type hash 0xbad_c0ffee_0dd_f00d 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, # Transfer ID 12345678901234567890 0x31, 0xD4, 0x00, 0x80, # Frame index, EOT 54321 with EOT flag set 0x12, 0x34, 0x56, 0x78, # Padding ignored ]) assert len(header) == 32 payload = b'Squeeze mayonnaise onto a hamster' f = SerialFrame.parse_from_unescaped_image(memoryview(header + payload + get_crc(header, payload)), ts) assert f == SerialFrame( priority=Priority.LOW, source_node_id=123, destination_node_id=456, data_specifier=MessageDataSpecifier(4321), data_type_hash=0xbad_c0ffee_0dd_f00d, transfer_id=12345678901234567890, index=54321, end_of_transfer=True, payload=memoryview(payload), timestamp=ts, ) # Valid service with no payload header = bytes([ _VERSION, int(Priority.LOW), 0x01, 0x00, 0x00, 0x00, 0x10, 0xC0, # Response, service ID 16 0x0D, 0xF0, 0xDD, 0xE0, 0xFE, 0x0F, 0xDC, 0xBA, 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, 0x31, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]) f = SerialFrame.parse_from_unescaped_image(memoryview(header + get_crc(header)), ts) assert f == SerialFrame( priority=Priority.LOW, source_node_id=1, destination_node_id=0, data_specifier=ServiceDataSpecifier(16, ServiceDataSpecifier.Role.RESPONSE), data_type_hash=0xbad_c0ffee_0dd_f00d, transfer_id=12345678901234567890, index=54321, end_of_transfer=False, payload=memoryview(b''), timestamp=ts, ) # Valid service with no payload header = bytes([ _VERSION, int(Priority.LOW), 0x01, 0x00, 0x00, 0x00, 0x10, 0x80, # Request, service ID 16 0x0D, 0xF0, 0xDD, 0xE0, 0xFE, 0x0F, 0xDC, 0xBA, 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, 0x31, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]) f = SerialFrame.parse_from_unescaped_image(memoryview(header + get_crc(header)), ts) assert f == SerialFrame( priority=Priority.LOW, source_node_id=1, destination_node_id=0, data_specifier=ServiceDataSpecifier(16, ServiceDataSpecifier.Role.REQUEST), data_type_hash=0xbad_c0ffee_0dd_f00d, transfer_id=12345678901234567890, index=54321, end_of_transfer=False, payload=memoryview(b''), timestamp=ts, ) # Too short assert SerialFrame.parse_from_unescaped_image(memoryview(header[1:] + get_crc(header, payload)), ts) is None # Bad CRC assert SerialFrame.parse_from_unescaped_image(memoryview(header + payload + b'1234'), ts) is None # Bad version header = bytes([ _VERSION + 1, int(Priority.LOW), 0xFF, 0xFF, 0x00, 0x00, 0xE1, 0x10, 0x0D, 0xF0, 0xDD, 0xE0, 0xFE, 0x0F, 0xDC, 0xBA, 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, 0x31, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]) assert SerialFrame.parse_from_unescaped_image(memoryview(header + get_crc(header)), ts) is None # Bad fields header = bytes([ _VERSION, 0x88, 0xFF, 0xFF, 0x00, 0xFF, 0xE1, 0x10, 0x0D, 0xF0, 0xDD, 0xE0, 0xFE, 0x0F, 0xDC, 0xBA, 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, 0x31, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]) assert SerialFrame.parse_from_unescaped_image(memoryview(header + get_crc(header)), ts) is None
async def _unittest_output_session_no_listener() -> None: """ Test the Windows-specific corner case. Should be handled identically on all platforms. """ ts = Timestamp.now() loop = asyncio.get_event_loop() loop.slow_callback_duration = 5.0 def make_sock() -> socket_.socket: sock = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM) sock.bind(("127.100.0.2", 0)) sock.connect( ("239.0.1.2", 33333)) # There is no listener on this endpoint. sock.setsockopt(socket_.IPPROTO_IP, socket_.IP_MULTICAST_IF, socket_.inet_aton("127.100.0.2")) sock.setblocking(False) return sock sos = UDPOutputSession( specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None), payload_metadata=PayloadMetadata(1024), mtu=11, multiplier=1, sock=make_sock(), finalizer=lambda: None, ) assert await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) sos.close() # Multi-frame with multiplication and feedback last_feedback: typing.Optional[Feedback] = None def feedback_handler(feedback: Feedback) -> None: nonlocal last_feedback assert last_feedback is None, "Unexpected feedback" last_feedback = feedback sos = UDPOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(1024), mtu=10, multiplier=2, sock=make_sock(), finalizer=lambda: None, ) sos.enable_feedback(feedback_handler) assert last_feedback is None assert await (sos.send( Transfer( timestamp=ts, priority=Priority.OPTIONAL, transfer_id=54321, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) print("last_feedback:", last_feedback) assert isinstance(last_feedback, UDPFeedback) # Ensure that the timestamp is populated even if the error suppression logic is activated. assert last_feedback.original_transfer_timestamp == ts assert Timestamp.now( ).monotonic >= last_feedback.first_frame_transmission_timestamp.monotonic >= ts.monotonic assert Timestamp.now( ).system >= last_feedback.first_frame_transmission_timestamp.system >= ts.system sos.close()
async def _unittest_serial_transport_capture(caplog: typing.Any) -> None: from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer from pyuavcan.transport import Priority, Timestamp, OutputSessionSpecifier get_monotonic = asyncio.get_event_loop().time tr = SerialTransport(serial_port="loop://", local_node_id=42, mtu=1024, service_transfer_multiplier=2) sft_capacity = 1024 payload_single = [_mem("qwertyui"), _mem("01234567") ] * (sft_capacity // 16) assert sum(map(len, payload_single)) == sft_capacity payload_x3 = (payload_single * 3)[:-1] payload_x3_size_bytes = sft_capacity * 3 - 8 assert sum(map(len, payload_x3)) == payload_x3_size_bytes broadcaster = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(2345), None), PayloadMetadata(10000)) client_requester = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST), 3210), PayloadMetadata(10000), ) events: typing.List[SerialCapture] = [] events2: typing.List[pyuavcan.transport.Capture] = [] def append_events(cap: pyuavcan.transport.Capture) -> None: assert isinstance(cap, SerialCapture) events.append(cap) tr.begin_capture(append_events) tr.begin_capture(events2.append) assert events == [] assert events2 == [] # # Multi-frame message. # ts = Timestamp.now() assert await broadcaster.send( Transfer(timestamp=ts, priority=Priority.LOW, transfer_id=777, fragmented_payload=payload_x3), monotonic_deadline=get_monotonic() + 5.0, ) await asyncio.sleep(0.1) assert events == events2 # Send three, receive three. # Sorting is required because the ordering of the events in the middle is not defined: arrival events # may or may not be registered before the emission event depending on how the serial loopback is operating. a, b, c, d, e, f = sorted(events, key=lambda x: not x.own) assert isinstance(a, SerialCapture) and a.own assert isinstance(b, SerialCapture) and b.own assert isinstance(c, SerialCapture) and c.own assert isinstance(d, SerialCapture) and not d.own assert isinstance(e, SerialCapture) and not e.own assert isinstance(f, SerialCapture) and not f.own def parse(x: SerialCapture) -> SerialFrame: out = SerialFrame.parse_from_cobs_image(x.fragment) assert out is not None return out assert parse(a).transfer_id == 777 assert parse(b).transfer_id == 777 assert parse(c).transfer_id == 777 assert a.timestamp.monotonic >= ts.monotonic assert b.timestamp.monotonic >= ts.monotonic assert c.timestamp.monotonic >= ts.monotonic assert parse(a).index == 0 assert parse(b).index == 1 assert parse(c).index == 2 assert not parse(a).end_of_transfer assert not parse(b).end_of_transfer assert parse(c).end_of_transfer assert a.fragment.tobytes().strip(b"\x00") == d.fragment.tobytes().strip( b"\x00") assert b.fragment.tobytes().strip(b"\x00") == e.fragment.tobytes().strip( b"\x00") assert c.fragment.tobytes().strip(b"\x00") == f.fragment.tobytes().strip( b"\x00") events.clear() events2.clear() # # Single-frame service request with dual frame duplication. # ts = Timestamp.now() assert await client_requester.send( Transfer(timestamp=ts, priority=Priority.HIGH, transfer_id=888, fragmented_payload=payload_single), monotonic_deadline=get_monotonic() + 5.0, ) await asyncio.sleep(0.1) assert events == events2 # Send two, receive two. # Sorting is required because the order of the two events in the middle is not defined: the arrival event # may or may not be registered before the emission event depending on how the serial loopback is operating. a, b, c, d = sorted(events, key=lambda x: not x.own) assert isinstance(a, SerialCapture) and a.own assert isinstance(b, SerialCapture) and b.own assert isinstance(c, SerialCapture) and not c.own assert isinstance(d, SerialCapture) and not d.own assert parse(a).transfer_id == 888 assert parse(b).transfer_id == 888 assert a.timestamp.monotonic >= ts.monotonic assert b.timestamp.monotonic >= ts.monotonic assert parse(a).index == 0 assert parse(b).index == 0 assert parse(a).end_of_transfer assert parse(b).end_of_transfer assert a.fragment.tobytes().strip(b"\x00") == c.fragment.tobytes().strip( b"\x00") assert b.fragment.tobytes().strip(b"\x00") == d.fragment.tobytes().strip( b"\x00") events.clear() events2.clear() # # Out-of-band data. # grownups = b"Aren't there any grownups at all? - No grownups!\x00" with caplog.at_level(logging.CRITICAL, logger=pyuavcan.transport.serial.__name__): # The frame delimiter is needed to force new frame into the state machine. tr.serial_port.write(grownups) await asyncio.sleep(1) assert events == events2 (oob, ) = events assert isinstance(oob, SerialCapture) assert not oob.own assert bytes(oob.fragment) == grownups events.clear() events2.clear()
async def _unittest_udp_transport() -> None: from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier from pyuavcan.transport import ProtocolParameters get_monotonic = asyncio.get_event_loop().time with pytest.raises(ValueError): _ = UDPTransport(ip_address='127.0.0.111/8', mtu=10) with pytest.raises(ValueError): _ = UDPTransport(ip_address='127.0.0.111/8', service_transfer_multiplier=100) tr = UDPTransport('127.0.0.111/8', mtu=9000) tr2 = UDPTransport('127.0.0.222/8', service_transfer_multiplier=2) assert tr.local_ip_address_with_netmask == '127.0.0.111/8' assert tr2.local_ip_address_with_netmask == '127.0.0.222/8' assert tr.loop is asyncio.get_event_loop() assert tr.local_node_id == 111 assert tr2.local_node_id == 222 assert tr.input_sessions == [] assert tr.output_sessions == [] assert list(xml.etree.ElementTree.fromstring( tr.descriptor).itertext()) == ['127.0.0.111/8'] assert tr.protocol_parameters == ProtocolParameters( transfer_id_modulo=2**56, max_nodes=2**UDPTransport.NODE_ID_BIT_LENGTH, mtu=9000, ) assert list(xml.etree.ElementTree.fromstring( tr2.descriptor).itertext()) == ['127.0.0.222/8'] assert tr2.protocol_parameters == ProtocolParameters( transfer_id_modulo=2**56, max_nodes=2**UDPTransport.NODE_ID_BIT_LENGTH, mtu=UDPTransport.DEFAULT_MTU, ) assert tr.sample_statistics() == tr2.sample_statistics( ) == UDPTransportStatistics() payload_single = [_mem('qwertyui'), _mem('01234567') ] * (UDPTransport.DEFAULT_MTU // 16) assert sum(map(len, payload_single)) == UDPTransport.DEFAULT_MTU payload_x3 = (payload_single * 3)[:-1] payload_x3_size_bytes = UDPTransport.DEFAULT_MTU * 3 - 8 assert sum(map(len, payload_x3)) == payload_x3_size_bytes # # Instantiate session objects. # meta = PayloadMetadata(0x_bad_c0ffee_0dd_f00d, 10000) broadcaster = tr2.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert broadcaster is tr2.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) subscriber_promiscuous = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta) assert subscriber_promiscuous is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta) subscriber_selective = tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 123), meta) assert subscriber_selective is tr.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), 123), meta) server_listener = tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), None), meta) assert server_listener is tr.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), None), meta) server_responder = tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 222), meta) assert server_responder is tr.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 222), meta) client_requester = tr2.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111), meta) assert client_requester is tr2.get_output_session( OutputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111), meta) client_listener = tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 111), meta) assert client_listener is tr2.get_input_session( InputSessionSpecifier( ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 111), meta) print('tr :', tr.input_sessions, tr.output_sessions) assert set(tr.input_sessions) == { subscriber_promiscuous, subscriber_selective, server_listener } assert set(tr.output_sessions) == {server_responder} print('tr2:', tr2.input_sessions, tr2.output_sessions) assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {broadcaster, client_requester} assert tr.sample_statistics().demultiplexer[MessageDataSpecifier( 12345)].accepted_datagrams == {} assert tr.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.REQUEST)].accepted_datagrams == {} assert tr2.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.RESPONSE)].accepted_datagrams == {} # # Message exchange test. # assert await broadcaster.send_until( Transfer(timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=77777, fragmented_payload=payload_single), monotonic_deadline=get_monotonic() + 5.0) rx_transfer = await subscriber_promiscuous.receive_until(get_monotonic() + 5.0) print('PROMISCUOUS SUBSCRIBER TRANSFER:', rx_transfer) assert isinstance(rx_transfer, TransferFrom) assert rx_transfer.priority == Priority.LOW assert rx_transfer.transfer_id == 77777 assert rx_transfer.fragmented_payload == [b''.join(payload_single) ] # type: ignore print('tr :', tr.sample_statistics()) assert tr.sample_statistics().demultiplexer[MessageDataSpecifier( 12345)].accepted_datagrams == { 222: 1 } assert tr.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.REQUEST)].accepted_datagrams == {} print('tr2:', tr2.sample_statistics()) assert tr2.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.RESPONSE)].accepted_datagrams == {} assert None is await subscriber_selective.receive_until(get_monotonic() + 0.1) assert None is await subscriber_promiscuous.receive_until(get_monotonic() + 0.1) assert None is await server_listener.receive_until(get_monotonic() + 0.1) assert None is await client_listener.receive_until(get_monotonic() + 0.1) # # Service exchange test. # assert await client_requester.send_until( Transfer(timestamp=Timestamp.now(), priority=Priority.HIGH, transfer_id=88888, fragmented_payload=payload_x3), monotonic_deadline=get_monotonic() + 5.0) rx_transfer = await server_listener.receive_until(get_monotonic() + 5.0) print('SERVER LISTENER TRANSFER:', rx_transfer) assert isinstance(rx_transfer, TransferFrom) assert rx_transfer.priority == Priority.HIGH assert rx_transfer.transfer_id == 88888 assert len(rx_transfer.fragmented_payload) == 3 assert b''.join(rx_transfer.fragmented_payload) == b''.join(payload_x3) assert None is await subscriber_selective.receive_until(get_monotonic() + 0.1) assert None is await subscriber_promiscuous.receive_until(get_monotonic() + 0.1) assert None is await server_listener.receive_until(get_monotonic() + 0.1) assert None is await client_listener.receive_until(get_monotonic() + 0.1) print('tr :', tr.sample_statistics()) assert tr.sample_statistics().demultiplexer[MessageDataSpecifier( 12345)].accepted_datagrams == { 222: 1 } assert tr.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.REQUEST )].accepted_datagrams == { 222: 3 * 2 } # Deterministic data loss mitigation is enabled, multiplication factor 2 print('tr2:', tr2.sample_statistics()) assert tr2.sample_statistics().demultiplexer[ServiceDataSpecifier( 444, ServiceDataSpecifier.Role.RESPONSE)].accepted_datagrams == {} # # Termination. # assert set(tr.input_sessions) == { subscriber_promiscuous, subscriber_selective, server_listener } assert set(tr.output_sessions) == {server_responder} assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {broadcaster, client_requester} subscriber_promiscuous.close() subscriber_promiscuous.close() # Idempotency. assert set(tr.input_sessions) == {subscriber_selective, server_listener} assert set(tr.output_sessions) == {server_responder} assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {broadcaster, client_requester} broadcaster.close() broadcaster.close() # Idempotency. assert set(tr.input_sessions) == {subscriber_selective, server_listener} assert set(tr.output_sessions) == {server_responder} assert set(tr2.input_sessions) == {client_listener} assert set(tr2.output_sessions) == {client_requester} tr.close() tr.close() # Idempotency. tr2.close() tr2.close() # Idempotency. assert not set(tr.input_sessions) assert not set(tr.output_sessions) assert not set(tr2.input_sessions) assert not set(tr2.output_sessions) with pytest.raises(pyuavcan.transport.ResourceClosedError): _ = tr.get_output_session( OutputSessionSpecifier(MessageDataSpecifier(12345), None), meta) with pytest.raises(pyuavcan.transport.ResourceClosedError): _ = tr2.get_input_session( InputSessionSpecifier(MessageDataSpecifier(12345), None), meta)
async def _unittest_output_session() -> None: ts = Timestamp.now() loop = asyncio.get_event_loop() tx_timestamp: typing.Optional[Timestamp] = Timestamp.now() tx_exception: typing.Optional[Exception] = None last_sent_frames: typing.List[SerialFrame] = [] last_monotonic_deadline = 0.0 finalized = False async def do_send(frames: typing.Sequence[SerialFrame], monotonic_deadline: float) -> typing.Optional[Timestamp]: nonlocal last_sent_frames nonlocal last_monotonic_deadline last_sent_frames = list(frames) last_monotonic_deadline = monotonic_deadline if tx_exception: raise tx_exception return tx_timestamp def do_finalize() -> None: nonlocal finalized finalized = True with raises(pyuavcan.transport.OperationNotDefinedForAnonymousNodeError): SerialOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 1111), payload_metadata=PayloadMetadata(1024), mtu=10, local_node_id=None, send_handler=do_send, finalizer=do_finalize, ) sos = SerialOutputSession( specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None), payload_metadata=PayloadMetadata(1024), mtu=11, local_node_id=None, send_handler=do_send, finalizer=do_finalize, ) assert sos.specifier == OutputSessionSpecifier(MessageDataSpecifier(3210), None) assert sos.destination_node_id is None assert sos.payload_metadata == PayloadMetadata(1024) assert sos.sample_statistics() == SessionStatistics() assert await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), 999999999.999, )) assert last_monotonic_deadline == approx(999999999.999) assert len(last_sent_frames) == 1 with raises(pyuavcan.transport.OperationNotDefinedForAnonymousNodeError): await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three four five") ], ), loop.time() + 10.0, )) last_feedback: typing.Optional[Feedback] = None def feedback_handler(feedback: Feedback) -> None: nonlocal last_feedback last_feedback = feedback sos.enable_feedback(feedback_handler) assert last_feedback is None assert await (sos.send( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[]), 999999999.999)) assert last_monotonic_deadline == approx(999999999.999) assert len(last_sent_frames) == 1 assert last_feedback is not None assert last_feedback.original_transfer_timestamp == ts assert last_feedback.first_frame_transmission_timestamp == tx_timestamp sos.disable_feedback() sos.disable_feedback() # Idempotency check assert sos.sample_statistics() == SessionStatistics(transfers=2, frames=2, payload_bytes=11, errors=0, drops=0) assert not finalized sos.close() assert finalized finalized = False sos = SerialOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(1024), mtu=10, local_node_id=1234, send_handler=do_send, finalizer=do_finalize, ) # Induced failure tx_timestamp = None assert not await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), 999999999.999, )) assert last_monotonic_deadline == approx(999999999.999) assert len(last_sent_frames) == 2 assert sos.sample_statistics() == SessionStatistics(transfers=0, frames=0, payload_bytes=0, errors=0, drops=2) tx_exception = RuntimeError() with raises(RuntimeError): _ = await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) assert sos.sample_statistics() == SessionStatistics(transfers=0, frames=0, payload_bytes=0, errors=1, drops=2) assert not finalized sos.close() assert finalized sos.close() # Idempotency with raises(pyuavcan.transport.ResourceClosedError): await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, ))
def _unittest_serial_tracer() -> None: from pytest import raises, approx from pyuavcan.transport import Priority, MessageDataSpecifier from pyuavcan.transport.serial import SerialTransport tr = SerialTransport.make_tracer() ts = Timestamp.now() def tx( x: typing.Union[bytes, bytearray, memoryview]) -> typing.Optional[Trace]: return tr.update( SerialCapture(ts, SerialCapture.Direction.TX, memoryview(x))) def rx( x: typing.Union[bytes, bytearray, memoryview]) -> typing.Optional[Trace]: return tr.update( SerialCapture(ts, SerialCapture.Direction.RX, memoryview(x))) buf = SerialFrame( priority=Priority.SLOW, transfer_id=1234567890, index=0, end_of_transfer=True, payload=memoryview(b"abc"), source_node_id=1111, destination_node_id=None, data_specifier=MessageDataSpecifier(6666), ).compile_into(bytearray(100)) head, tail = buf[:10], buf[10:] assert None is tx(head) # Semi-complete. trace = tx(head) # Double-head invalidates the previous one. assert isinstance(trace, SerialOutOfBandTrace) assert trace.timestamp == ts assert trace.data.tobytes().strip(b"\0") == head.tobytes().strip(b"\0") trace = tx(tail) assert isinstance(trace, TransferTrace) assert trace.timestamp == ts assert trace.transfer_id_timeout == approx( AlienTransferReassembler.MAX_TRANSFER_ID_TIMEOUT) # Initial value. assert trace.transfer.metadata.transfer_id == 1234567890 assert trace.transfer.metadata.priority == Priority.SLOW assert trace.transfer.metadata.session_specifier.source_node_id == 1111 assert trace.transfer.metadata.session_specifier.destination_node_id is None assert trace.transfer.metadata.session_specifier.data_specifier == MessageDataSpecifier( 6666) assert trace.transfer.fragmented_payload == [memoryview(b"abc")] buf = SerialFrame( priority=Priority.SLOW, transfer_id=1234567890, index=0, end_of_transfer=True, payload=memoryview(b"abc"), source_node_id=None, destination_node_id=None, data_specifier=MessageDataSpecifier(6666), ).compile_into(bytearray(100)) trace = rx(buf) assert isinstance(trace, TransferTrace) assert trace.timestamp == ts assert trace.transfer.metadata.transfer_id == 1234567890 assert trace.transfer.metadata.session_specifier.source_node_id is None assert trace.transfer.metadata.session_specifier.destination_node_id is None assert None is tr.update( pyuavcan.transport.Capture(ts)) # Wrong type, ignore. trace = tx( SerialFrame( priority=Priority.SLOW, transfer_id=1234567890, index=0, end_of_transfer=False, payload=memoryview(bytes(range(256))), source_node_id=3333, destination_node_id=None, data_specifier=MessageDataSpecifier(6666), ).compile_into(bytearray(10_000))) assert trace is None trace = tx( SerialFrame( priority=Priority.SLOW, transfer_id=1234567890, index=1, end_of_transfer=True, payload=memoryview(bytes(range(256))), source_node_id=3333, destination_node_id=None, data_specifier=MessageDataSpecifier(6666), ).compile_into(bytearray(10_000))) assert isinstance(trace, SerialErrorTrace) assert trace.error == TransferReassembler.Error.MULTIFRAME_INTEGRITY_ERROR with raises(ValueError, match=".*delimiters.*"): rx(b"".join([buf, buf]))
def _unittest_frame_parse() -> None: from pyuavcan.transport import Priority, MessageDataSpecifier, ServiceDataSpecifier def get_crc(*blocks: typing.Union[bytes, memoryview]) -> bytes: return pyuavcan.transport.commons.crc.CRC32C.new(*blocks).value_as_bytes # Valid message with payload header = bytes( [ _VERSION, int(Priority.LOW), 0x7B, 0x00, # Source NID 123 0xC8, 0x01, # Destination NID 456 0xE1, 0x10, # Data specifier 4321 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # Reserved 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, # Transfer ID 12345678901234567890 0x31, 0xD4, 0x00, 0x80, # Frame index, EOT 54321 with EOT flag set ] ) header += get_crc(header) assert len(header) == 32 payload = b"Squeeze mayonnaise onto a hamster" f = SerialFrame.parse_from_unescaped_image(memoryview(header + payload + get_crc(payload))) assert f == SerialFrame( priority=Priority.LOW, source_node_id=123, destination_node_id=456, data_specifier=MessageDataSpecifier(4321), transfer_id=12345678901234567890, index=54321, end_of_transfer=True, payload=memoryview(payload), ) # Valid service with no payload header = bytes( [ _VERSION, int(Priority.LOW), 0x01, 0x00, 0x00, 0x00, 0x10, 0xC0, # Response, service ID 16 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, 0x31, 0xD4, 0x00, 0x00, ] ) header += get_crc(header) assert len(header) == 32 f = SerialFrame.parse_from_unescaped_image(memoryview(header + get_crc(b""))) assert f == SerialFrame( priority=Priority.LOW, source_node_id=1, destination_node_id=0, data_specifier=ServiceDataSpecifier(16, ServiceDataSpecifier.Role.RESPONSE), transfer_id=12345678901234567890, index=54321, end_of_transfer=False, payload=memoryview(b""), ) # Valid service with no payload header = bytes( [ _VERSION, int(Priority.LOW), 0x01, 0x00, 0x00, 0x00, 0x10, 0x80, # Request, service ID 16 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, 0x31, 0xD4, 0x00, 0x00, ] ) header += get_crc(header) assert len(header) == 32 f = SerialFrame.parse_from_unescaped_image(memoryview(header + get_crc(b""))) assert f == SerialFrame( priority=Priority.LOW, source_node_id=1, destination_node_id=0, data_specifier=ServiceDataSpecifier(16, ServiceDataSpecifier.Role.REQUEST), transfer_id=12345678901234567890, index=54321, end_of_transfer=False, payload=memoryview(b""), ) # Too short assert SerialFrame.parse_from_unescaped_image(memoryview(header[1:] + get_crc(payload))) is None # Bad CRC assert SerialFrame.parse_from_unescaped_image(memoryview(header + payload + b"1234")) is None # Bad version header = bytes( [ _VERSION + 1, int(Priority.LOW), 0xFF, 0xFF, 0x00, 0x00, 0xE1, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, 0x31, 0xD4, 0x00, 0x00, ] ) header += get_crc(header) assert len(header) == 32 assert SerialFrame.parse_from_unescaped_image(memoryview(header + get_crc(b""))) is None # Bad fields header = bytes( [ _VERSION, 0x88, 0xFF, 0xFF, 0x00, 0xFF, 0xE1, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0x0A, 0x1F, 0xEB, 0x8C, 0xA9, 0x54, 0xAB, 0x31, 0xD4, 0x00, 0x00, ] ) header += get_crc(header) assert len(header) == 32 assert SerialFrame.parse_from_unescaped_image(memoryview(header + get_crc(b""))) is None
def _unittest_input_session() -> None: import asyncio from pytest import raises, approx from pyuavcan.transport import InputSessionSpecifier, MessageDataSpecifier, Priority, TransferFrom from pyuavcan.transport import PayloadMetadata, Timestamp from pyuavcan.transport.commons.high_overhead_transport import TransferCRC ts = Timestamp.now() prio = Priority.SLOW dst_nid = 1234 run_until_complete = asyncio.get_event_loop().run_until_complete get_monotonic = asyncio.get_event_loop().time nihil_supernum = b'nihil supernum' finalized = False def do_finalize() -> None: nonlocal finalized finalized = True session_spec = InputSessionSpecifier(MessageDataSpecifier(12345), None) payload_meta = PayloadMetadata(0xdead_beef_bad_c0ffe, 100) sis = SerialInputSession(specifier=session_spec, payload_metadata=payload_meta, loop=asyncio.get_event_loop(), finalizer=do_finalize) assert sis.specifier == session_spec assert sis.payload_metadata == payload_meta assert sis.sample_statistics() == SerialInputSessionStatistics() assert sis.transfer_id_timeout == approx( SerialInputSession.DEFAULT_TRANSFER_ID_TIMEOUT) sis.transfer_id_timeout = 1.0 with raises(ValueError): sis.transfer_id_timeout = 0.0 assert sis.transfer_id_timeout == approx(1.0) assert run_until_complete(sis.receive_until(get_monotonic() + 0.1)) is None assert run_until_complete(sis.receive_until(0.0)) is None def mk_frame(transfer_id: int, index: int, end_of_transfer: bool, payload: typing.Union[bytes, memoryview], source_node_id: typing.Optional[int]) -> SerialFrame: return SerialFrame(timestamp=ts, priority=prio, transfer_id=transfer_id, index=index, end_of_transfer=end_of_transfer, payload=memoryview(payload), source_node_id=source_node_id, destination_node_id=dst_nid, data_specifier=session_spec.data_specifier, data_type_hash=payload_meta.data_type_hash) # ANONYMOUS TRANSFERS. sis._process_frame( mk_frame(transfer_id=0, index=0, end_of_transfer=False, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( frames=1, errors=1, ) sis._process_frame( mk_frame(transfer_id=0, index=1, end_of_transfer=True, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( frames=2, errors=2, ) sis._process_frame( mk_frame(transfer_id=0, index=0, end_of_transfer=True, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=1, frames=3, payload_bytes=len(nihil_supernum), errors=2, ) assert run_until_complete(sis.receive_until(0)) == \ TransferFrom(timestamp=ts, priority=prio, transfer_id=0, fragmented_payload=[memoryview(nihil_supernum)], source_node_id=None) assert run_until_complete(sis.receive_until(get_monotonic() + 0.1)) is None assert run_until_complete(sis.receive_until(0.0)) is None # BAD DATA TYPE HASH. sis._process_frame( SerialFrame(timestamp=ts, priority=prio, transfer_id=0, index=0, end_of_transfer=True, payload=memoryview(nihil_supernum), source_node_id=None, destination_node_id=None, data_specifier=session_spec.data_specifier, data_type_hash=0xbad_bad_bad_bad_bad)) assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=1, frames=4, payload_bytes=len(nihil_supernum), errors=3, mismatched_data_type_hashes={0xbad_bad_bad_bad_bad: 1}, )
async def _unittest_input_session() -> None: ts = Timestamp.now() prio = Priority.SLOW dst_nid = 1234 get_monotonic = asyncio.get_event_loop().time nihil_supernum = b"nihil supernum" finalized = False def do_finalize() -> None: nonlocal finalized finalized = True session_spec = InputSessionSpecifier(MessageDataSpecifier(2345), None) payload_meta = PayloadMetadata(100) sis = SerialInputSession(specifier=session_spec, payload_metadata=payload_meta, finalizer=do_finalize) assert sis.specifier == session_spec assert sis.payload_metadata == payload_meta assert sis.sample_statistics() == SerialInputSessionStatistics() assert sis.transfer_id_timeout == approx( SerialInputSession.DEFAULT_TRANSFER_ID_TIMEOUT) sis.transfer_id_timeout = 1.0 with raises(ValueError): sis.transfer_id_timeout = 0.0 assert sis.transfer_id_timeout == approx(1.0) assert await (sis.receive(get_monotonic() + 0.1)) is None assert await (sis.receive(0.0)) is None def mk_frame( transfer_id: int, index: int, end_of_transfer: bool, payload: typing.Union[bytes, memoryview], source_node_id: typing.Optional[int], ) -> SerialFrame: return SerialFrame( priority=prio, transfer_id=transfer_id, index=index, end_of_transfer=end_of_transfer, payload=memoryview(payload), source_node_id=source_node_id, destination_node_id=dst_nid, data_specifier=session_spec.data_specifier, ) # ANONYMOUS TRANSFERS. sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=0, end_of_transfer=False, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( frames=1, errors=1, ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=1, end_of_transfer=True, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( frames=2, errors=2, ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=0, end_of_transfer=True, payload=nihil_supernum, source_node_id=None)) assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=1, frames=3, payload_bytes=len(nihil_supernum), errors=2, ) assert await (sis.receive(0)) == TransferFrom( timestamp=ts, priority=prio, transfer_id=0, fragmented_payload=[memoryview(nihil_supernum)], source_node_id=None) assert await (sis.receive(get_monotonic() + 0.1)) is None assert await (sis.receive(0.0)) is None # VALID TRANSFERS. Notice that they are unordered on purpose. The reassembler can deal with that. sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=1, end_of_transfer=False, payload=nihil_supernum, source_node_id=1111)) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=0, end_of_transfer=True, payload=nihil_supernum, source_node_id=2222)) # COMPLETED FIRST assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=2, frames=5, payload_bytes=len(nihil_supernum) * 2, errors=2, reassembly_errors_per_source_node_id={ 1111: {}, 2222: {}, }, ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame( transfer_id=0, index=3, end_of_transfer=True, payload=TransferCRC.new(nihil_supernum * 3).value_as_bytes, source_node_id=1111, ), ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=0, end_of_transfer=False, payload=nihil_supernum, source_node_id=1111)) sis._process_frame( # pylint: disable=protected-access ts, mk_frame(transfer_id=0, index=2, end_of_transfer=False, payload=nihil_supernum, source_node_id=1111)) # COMPLETED SECOND assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=3, frames=8, payload_bytes=len(nihil_supernum) * 5, errors=2, reassembly_errors_per_source_node_id={ 1111: {}, 2222: {}, }, ) assert await (sis.receive(0)) == TransferFrom( timestamp=ts, priority=prio, transfer_id=0, fragmented_payload=[memoryview(nihil_supernum)], source_node_id=2222) assert await (sis.receive(0)) == TransferFrom( timestamp=ts, priority=prio, transfer_id=0, fragmented_payload=[memoryview(nihil_supernum)] * 3, source_node_id=1111, ) assert await (sis.receive(get_monotonic() + 0.1)) is None assert await (sis.receive(0.0)) is None # TRANSFERS WITH REASSEMBLY ERRORS. sis._process_frame( # pylint: disable=protected-access ts, mk_frame( transfer_id=1, index=0, end_of_transfer=False, payload=b"", source_node_id=1111 # EMPTY IN MULTIFRAME ), ) sis._process_frame( # pylint: disable=protected-access ts, mk_frame( transfer_id=2, index=0, end_of_transfer=False, payload=b"", source_node_id=1111 # EMPTY IN MULTIFRAME ), ) assert sis.sample_statistics() == SerialInputSessionStatistics( transfers=3, frames=10, payload_bytes=len(nihil_supernum) * 5, errors=4, reassembly_errors_per_source_node_id={ 1111: { TransferReassembler.Error.MULTIFRAME_EMPTY_FRAME: 2, }, 2222: {}, }, ) assert not finalized sis.close() assert finalized sis.close() # Idempotency check
def _unittest_output_session() -> None: from pytest import raises from pyuavcan.transport import OutputSessionSpecifier, MessageDataSpecifier, ServiceDataSpecifier, Priority from pyuavcan.transport import PayloadMetadata, SessionStatistics, Timestamp, Feedback, Transfer ts = Timestamp.now() loop = asyncio.get_event_loop() run_until_complete = loop.run_until_complete finalized = False def do_finalize() -> None: nonlocal finalized finalized = True def check_timestamp(t: pyuavcan.transport.Timestamp) -> bool: now = pyuavcan.transport.Timestamp.now() s = ts.system_ns <= t.system_ns <= now.system_ns m = ts.monotonic_ns <= t.monotonic_ns <= now.system_ns return s and m destination_endpoint = '127.100.0.1', 25406 sock_rx = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM) sock_rx.bind(destination_endpoint) sock_rx.settimeout(1.0) def make_sock() -> socket_.socket: sock = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM) sock.bind(('127.100.0.2', 0)) sock.connect(destination_endpoint) sock.setblocking(False) return sock sos = UDPOutputSession( specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None), payload_metadata=PayloadMetadata(0xdead_beef_badc0ffe, 1024), mtu=11, multiplier=1, sock=make_sock(), loop=asyncio.get_event_loop(), finalizer=do_finalize, ) assert sos.specifier == OutputSessionSpecifier(MessageDataSpecifier(3210), None) assert sos.destination_node_id is None assert sos.payload_metadata == PayloadMetadata(0xdead_beef_badc0ffe, 1024) assert sos.sample_statistics() == SessionStatistics() assert run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() + 10.0)) rx_data, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' assert rx_data == ( b'\x00\x04\x00\x00\x00\x00\x00\x8040\x00\x00\x00\x00\x00\x00\xfe\x0f\xdc\xba\xef\xbe\xad\xde' + b'one' b'two' b'three') with raises(socket_.timeout): sock_rx.recvfrom(1000) last_feedback: typing.Optional[Feedback] = None def feedback_handler(feedback: Feedback) -> None: nonlocal last_feedback last_feedback = feedback sos.enable_feedback(feedback_handler) assert last_feedback is None assert run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[]), loop.time() + 10.0)) assert last_feedback is not None assert last_feedback.original_transfer_timestamp == ts assert check_timestamp(last_feedback.first_frame_transmission_timestamp) sos.disable_feedback() sos.disable_feedback() # Idempotency check _, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' with raises(socket_.timeout): sock_rx.recvfrom(1000) assert sos.sample_statistics() == SessionStatistics(transfers=2, frames=2, payload_bytes=11, errors=0, drops=0) assert sos.socket.fileno() >= 0 assert not finalized sos.close() assert finalized assert sos.socket.fileno() < 0 # The socket is supposed to be disposed of. finalized = False # Multi-frame with multiplication sos = UDPOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(0xdead_beef_badc0ffe, 1024), mtu=10, multiplier=2, sock=make_sock(), loop=asyncio.get_event_loop(), finalizer=do_finalize, ) assert run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.OPTIONAL, transfer_id=54321, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() + 10.0)) data_main_a, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' data_main_b, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' data_redundant_a, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' data_redundant_b, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == '127.100.0.2' with raises(socket_.timeout): sock_rx.recvfrom(1000) print('data_main_a', data_main_a) print('data_main_b', data_main_b) print('data_redundant_a', data_redundant_a) print('data_redundant_b', data_redundant_b) assert data_main_a == data_redundant_a assert data_main_b == data_redundant_b assert data_main_a == ( b'\x00\x07\x00\x00\x00\x00\x00\x001\xd4\x00\x00\x00\x00\x00\x00\xfe\x0f\xdc\xba\xef\xbe\xad\xde' + b'one' b'two' b'three'[:-1]) assert data_main_b == ( b'\x00\x07\x00\x00\x01\x00\x00\x801\xd4\x00\x00\x00\x00\x00\x00\xfe\x0f\xdc\xba\xef\xbe\xad\xde' + b'e' + pyuavcan.transport.commons.crc.CRC32C.new( b'one', b'two', b'three').value_as_bytes) sos = UDPOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(0xdead_beef_badc0ffe, 1024), mtu=10, multiplier=1, sock=make_sock(), loop=asyncio.get_event_loop(), finalizer=do_finalize, ) # Induced timeout assert not run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() - 0.1 # Expired on arrival )) assert sos.sample_statistics() == SessionStatistics( transfers=0, frames=0, payload_bytes=0, errors=0, drops=2 # Because multiframe ) # Induced failure sos.socket.close() with raises(OSError): assert not run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() + 10.0)) assert sos.sample_statistics() == SessionStatistics(transfers=0, frames=0, payload_bytes=0, errors=1, drops=2) assert not finalized sos.close() assert finalized sos.close() # Idempotency with raises(pyuavcan.transport.ResourceClosedError): run_until_complete( sos.send_until( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b'one'), memoryview(b'two'), memoryview(b'three') ]), loop.time() + 10.0)) sock_rx.close()
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
def _unittest_socket_factory() -> None: from pytest import raises from ipaddress import ip_address is_linux = sys.platform.startswith("linux") fac = SocketFactory.new(ip_address("127.42.1.200")) assert fac.max_nodes == 0xFFFF assert str(fac.local_ip_address) == "127.42.1.200" # SERVICE SOCKET TEST (unicast) ds = ServiceDataSpecifier(100, ServiceDataSpecifier.Role.REQUEST) test_u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) test_u.bind(("127.42.0.123", service_data_specifier_to_udp_port(ds))) srv_o = fac.make_output_socket(123, ds) srv_o.send(b"Goose") rx = test_u.recvfrom(1024) assert rx[0] == b"Goose" assert rx[1][0] == "127.42.1.200" srv_i = fac.make_input_socket(ds) test_u.sendto(b"Duck", ("127.42.1.200", service_data_specifier_to_udp_port(ds))) rx = srv_i.recvfrom(1024) assert rx[0] == b"Duck" assert rx[1][0] == "127.42.0.123" test_u.close() # MESSAGE SOCKET TEST (multicast) # Note that Windows does not permit using the same socket for both sending to and receiving from a unicast group # because in order to specify a particular output interface the socket must be bound to a unicast address. # So we set up separate sockets for input and output. test_i = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) test_i.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) test_i.bind(("239.42.2.100" * is_linux, SUBJECT_PORT)) test_i.setsockopt( socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton("239.42.2.100") + socket.inet_aton("127.42.0.123")) msg_o = fac.make_output_socket( None, MessageDataSpecifier(612)) # 612 = (2 << 8) + 100 msg_o.send(b"Eagle") rx = test_i.recvfrom(1024) assert rx[0] == b"Eagle" assert rx[1][0] == "127.42.1.200" test_o = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) test_o.bind(("127.42.0.123", 0)) test_o.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton("127.42.0.123")) msg_i = fac.make_input_socket(MessageDataSpecifier(612)) test_o.sendto(b"Seagull", ("239.42.2.100", SUBJECT_PORT)) rx = msg_i.recvfrom(1024) assert rx[0] == b"Seagull" assert rx[1][0] == "127.42.0.123" # Same address we just bound to. # ERRORS with raises(InvalidMediaConfigurationError): IPv4SocketFactory(ip_address("1.2.3.4")).make_input_socket( ServiceDataSpecifier(0, ServiceDataSpecifier.Role.RESPONSE)) with raises(InvalidMediaConfigurationError): IPv4SocketFactory(ip_address("1.2.3.4")).make_input_socket( MessageDataSpecifier(0)) with raises(InvalidMediaConfigurationError): IPv4SocketFactory(ip_address("1.2.3.4")).make_output_socket( 1, ServiceDataSpecifier(0, ServiceDataSpecifier.Role.RESPONSE)) with raises(InvalidMediaConfigurationError): IPv4SocketFactory(ip_address("1.2.3.4")).make_output_socket( 1, MessageDataSpecifier(0)) with raises(UnsupportedSessionConfigurationError): fac.make_output_socket(1, MessageDataSpecifier(0)) with raises(UnsupportedSessionConfigurationError): fac.make_output_socket( None, ServiceDataSpecifier(0, ServiceDataSpecifier.Role.RESPONSE)) # CLEAN UP test_u.close() test_i.close() test_o.close() srv_o.close() srv_i.close() msg_o.close() msg_i.close()
def _unittest_frame_check() -> None: from pytest import raises from pyuavcan.transport import Priority, MessageDataSpecifier, ServiceDataSpecifier _ = SerialFrame( priority=Priority.HIGH, source_node_id=123, destination_node_id=456, data_specifier=MessageDataSpecifier(2345), transfer_id=1234567890123456789, index=1234567, end_of_transfer=False, payload=memoryview(b"abcdef"), ) with raises(ValueError): SerialFrame( priority=Priority.HIGH, source_node_id=123456, destination_node_id=456, data_specifier=MessageDataSpecifier(2345), transfer_id=1234567890123456789, index=1234567, end_of_transfer=False, payload=memoryview(b"abcdef"), ) with raises(ValueError): SerialFrame( priority=Priority.HIGH, source_node_id=123, destination_node_id=123456, data_specifier=MessageDataSpecifier(2345), transfer_id=1234567890123456789, index=1234567, end_of_transfer=False, payload=memoryview(b"abcdef"), ) with raises(ValueError): SerialFrame( priority=Priority.HIGH, source_node_id=None, destination_node_id=456, data_specifier=ServiceDataSpecifier(123, ServiceDataSpecifier.Role.REQUEST), transfer_id=1234567890123456789, index=1234567, end_of_transfer=False, payload=memoryview(b"abcdef"), ) with raises(ValueError): SerialFrame( priority=Priority.HIGH, source_node_id=None, destination_node_id=None, data_specifier=MessageDataSpecifier(2345), transfer_id=-1, index=1234567, end_of_transfer=False, payload=memoryview(b"abcdef"), ) with raises(ValueError): SerialFrame( priority=Priority.HIGH, source_node_id=None, destination_node_id=None, data_specifier=MessageDataSpecifier(2345), transfer_id=0, index=-1, end_of_transfer=False, payload=memoryview(b"abcdef"), )
async def _unittest_output_session() -> None: ts = Timestamp.now() loop = asyncio.get_event_loop() loop.slow_callback_duration = 5.0 # TODO use asyncio socket read and remove this thing. finalized = False def do_finalize() -> None: nonlocal finalized finalized = True def check_timestamp(t: Timestamp) -> bool: now = Timestamp.now() s = ts.system_ns <= t.system_ns <= now.system_ns m = ts.monotonic_ns <= t.monotonic_ns <= now.system_ns return s and m destination_endpoint = "127.100.0.1", 25406 sock_rx = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM) sock_rx.bind(destination_endpoint) sock_rx.settimeout(1.0) def make_sock() -> socket_.socket: sock = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM) sock.bind(("127.100.0.2", 0)) sock.connect(destination_endpoint) sock.setblocking(False) return sock sos = UDPOutputSession( specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None), payload_metadata=PayloadMetadata(1024), mtu=11, multiplier=1, sock=make_sock(), finalizer=do_finalize, ) assert sos.specifier == OutputSessionSpecifier(MessageDataSpecifier(3210), None) assert sos.destination_node_id is None assert sos.payload_metadata == PayloadMetadata(1024) assert sos.sample_statistics() == SessionStatistics() assert await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) rx_data, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" assert rx_data == ( b"\x00\x04\x00\x00\x00\x00\x00\x8040\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"one" + b"two" + b"three") with raises(socket_.timeout): sock_rx.recvfrom(1000) last_feedback: typing.Optional[Feedback] = None def feedback_handler(feedback: Feedback) -> None: nonlocal last_feedback last_feedback = feedback sos.enable_feedback(feedback_handler) assert last_feedback is None assert await (sos.send( Transfer(timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[]), loop.time() + 10.0, )) assert last_feedback is not None assert last_feedback.original_transfer_timestamp == ts assert check_timestamp(last_feedback.first_frame_transmission_timestamp) sos.disable_feedback() sos.disable_feedback() # Idempotency check _, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" with raises(socket_.timeout): sock_rx.recvfrom(1000) assert sos.sample_statistics() == SessionStatistics(transfers=2, frames=2, payload_bytes=11, errors=0, drops=0) assert sos.socket.fileno() >= 0 assert not finalized sos.close() assert finalized assert sos.socket.fileno() < 0 # The socket is supposed to be disposed of. finalized = False # Multi-frame with multiplication sos = UDPOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(1024), mtu=10, multiplier=2, sock=make_sock(), finalizer=do_finalize, ) assert await (sos.send( Transfer( timestamp=ts, priority=Priority.OPTIONAL, transfer_id=54321, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) data_main_a, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" data_main_b, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" data_redundant_a, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" data_redundant_b, endpoint = sock_rx.recvfrom(1000) assert endpoint[0] == "127.100.0.2" with raises(socket_.timeout): sock_rx.recvfrom(1000) print("data_main_a", data_main_a) print("data_main_b", data_main_b) print("data_redundant_a", data_redundant_a) print("data_redundant_b", data_redundant_b) assert data_main_a == data_redundant_a assert data_main_b == data_redundant_b assert data_main_a == ( b"\x00\x07\x00\x00\x00\x00\x00\x001\xd4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"one" + b"two" + b"three"[:-1]) assert data_main_b == ( b"\x00\x07\x00\x00\x01\x00\x00\x801\xd4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"e" + pyuavcan.transport.commons.crc.CRC32C.new( b"one", b"two", b"three").value_as_bytes) sos.socket.close() # This is to prevent resource warning sos = UDPOutputSession( specifier=OutputSessionSpecifier( ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST), 2222), payload_metadata=PayloadMetadata(1024), mtu=10, multiplier=1, sock=make_sock(), finalizer=do_finalize, ) # Induced timeout assert not await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() - 0.1, # Expired on arrival )) assert sos.sample_statistics() == SessionStatistics( transfers=0, frames=0, payload_bytes=0, errors=0, drops=2 # Because multiframe ) # Induced failure sos.socket.close() with raises(OSError): assert not await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) assert sos.sample_statistics() == SessionStatistics(transfers=0, frames=0, payload_bytes=0, errors=1, drops=2) assert not finalized sos.close() assert finalized sos.close() # Idempotency with raises(pyuavcan.transport.ResourceClosedError): await (sos.send( Transfer( timestamp=ts, priority=Priority.NOMINAL, transfer_id=12340, fragmented_payload=[ memoryview(b"one"), memoryview(b"two"), memoryview(b"three") ], ), loop.time() + 10.0, )) sock_rx.close()