Beispiel #1
0
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)
Beispiel #2
0
async def _unittest_loopback_transport_service() -> None:
    from pyuavcan.transport import ServiceDataSpecifier, InputSessionSpecifier, OutputSessionSpecifier

    payload_metadata = pyuavcan.transport.PayloadMetadata(
        0xdeadbeef0ddf00d, 1234)

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

    inp = tr.get_input_session(
        InputSessionSpecifier(
            ServiceDataSpecifier(123, ServiceDataSpecifier.Role.REQUEST),
            1234), payload_metadata)

    out = tr.get_output_session(
        OutputSessionSpecifier(
            ServiceDataSpecifier(123, ServiceDataSpecifier.Role.REQUEST),
            1234), payload_metadata)

    assert await out.send_until(
        pyuavcan.transport.Transfer(
            timestamp=pyuavcan.transport.Timestamp.now(),
            priority=pyuavcan.transport.Priority.IMMEDIATE,
            transfer_id=123,  # mod 32 = 27
            fragmented_payload=[memoryview(b'Hello world!')],
        ),
        tr.loop.time() + 1.0)

    assert None is not await inp.receive_until(0)
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_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
Beispiel #5
0
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()
Beispiel #6
0
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.
Beispiel #7
0
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)
Beispiel #8
0
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()
Beispiel #9
0
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},
    )
Beispiel #10
0
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
Beispiel #11
0
async def _unittest_serial_transport(caplog: typing.Any) -> 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

    service_multiplication_factor = 2

    with pytest.raises(ValueError):
        _ = SerialTransport(serial_port="loop://", local_node_id=None, mtu=1)

    with pytest.raises(ValueError):
        _ = SerialTransport(serial_port="loop://",
                            local_node_id=None,
                            service_transfer_multiplier=10000)

    with pytest.raises(pyuavcan.transport.InvalidMediaConfigurationError):
        _ = SerialTransport(serial_port=serial.serial_for_url(
            "loop://", do_not_open=True),
                            local_node_id=None)

    tr = SerialTransport(serial_port="loop://", local_node_id=None, mtu=1024)

    assert tr.local_node_id is None
    assert tr.serial_port.is_open

    assert tr.input_sessions == []
    assert tr.output_sessions == []

    assert tr.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2**64,
        max_nodes=4096,
        mtu=1024,
    )

    assert tr.sample_statistics() == SerialTransportStatistics()

    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

    #
    # Instantiate session objects.
    #
    meta = PayloadMetadata(10000)

    broadcaster = tr.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    assert broadcaster is tr.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    subscriber_promiscuous = tr.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    assert subscriber_promiscuous is tr.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    subscriber_selective = tr.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta)
    assert subscriber_selective is tr.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), 3210), 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)

    client_listener = tr.get_input_session(
        InputSessionSpecifier(
            ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE),
            3210), meta)
    assert client_listener is tr.get_input_session(
        InputSessionSpecifier(
            ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE),
            3210), meta)

    print("INPUTS:", tr.input_sessions)
    print("OUTPUTS:", tr.output_sessions)
    assert set(tr.input_sessions) == {
        subscriber_promiscuous, subscriber_selective, server_listener,
        client_listener
    }
    assert set(tr.output_sessions) == {broadcaster}
    assert tr.sample_statistics() == SerialTransportStatistics()

    #
    # Message exchange test.
    #
    assert await broadcaster.send(
        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(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)]

    print(tr.sample_statistics())
    assert tr.sample_statistics().in_bytes >= 32 + sft_capacity + 2
    assert tr.sample_statistics().in_frames == 1
    assert tr.sample_statistics().in_out_of_band_bytes == 0
    assert tr.sample_statistics().out_bytes == tr.sample_statistics().in_bytes
    assert tr.sample_statistics().out_frames == 1
    assert tr.sample_statistics().out_transfers == 1
    assert tr.sample_statistics().out_incomplete == 0

    with pytest.raises(
            pyuavcan.transport.OperationNotDefinedForAnonymousNodeError):
        # Anonymous nodes can't send multiframe transfers.
        assert await broadcaster.send(
            Transfer(timestamp=Timestamp.now(),
                     priority=Priority.LOW,
                     transfer_id=77777,
                     fragmented_payload=payload_x3),
            monotonic_deadline=get_monotonic() + 5.0,
        )

    assert None is await subscriber_selective.receive(get_monotonic() + 0.1)
    assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1)
    assert None is await server_listener.receive(get_monotonic() + 0.1)
    assert None is await client_listener.receive(get_monotonic() + 0.1)

    #
    # Service exchange test.
    #
    with pytest.raises(
            pyuavcan.transport.OperationNotDefinedForAnonymousNodeError):
        # Anonymous nodes can't emit service transfers.
        tr.get_output_session(
            OutputSessionSpecifier(
                ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST),
                3210), meta)

    #
    # Replace the transport with a different one where the local node-ID is not None.
    #
    tr = SerialTransport(serial_port="loop://", local_node_id=3210, mtu=1024)
    assert tr.local_node_id == 3210

    #
    # Re-instantiate session objects because the transport instances have been replaced.
    #
    broadcaster = tr.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    assert broadcaster is tr.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    subscriber_promiscuous = tr.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    subscriber_selective = tr.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), 3210), 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),
            3210), meta)
    assert server_responder is tr.get_output_session(
        OutputSessionSpecifier(
            ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE),
            3210), meta)

    client_requester = tr.get_output_session(
        OutputSessionSpecifier(
            ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST),
            3210), meta)
    assert client_requester is tr.get_output_session(
        OutputSessionSpecifier(
            ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST),
            3210), meta)

    client_listener = tr.get_input_session(
        InputSessionSpecifier(
            ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE),
            3210), meta)
    assert client_listener is tr.get_input_session(
        InputSessionSpecifier(
            ServiceDataSpecifier(333, ServiceDataSpecifier.Role.RESPONSE),
            3210), meta)

    assert set(tr.input_sessions) == {
        subscriber_promiscuous, subscriber_selective, server_listener,
        client_listener
    }
    assert set(tr.output_sessions) == {
        broadcaster, server_responder, client_requester
    }
    assert tr.sample_statistics() == SerialTransportStatistics()

    assert await client_requester.send(
        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(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(get_monotonic() + 0.1)
    assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1)
    assert None is await server_listener.receive(get_monotonic() + 0.1)
    assert None is await client_listener.receive(get_monotonic() + 0.1)

    print(tr.sample_statistics())
    assert tr.sample_statistics().in_bytes >= (
        32 * 3 + payload_x3_size_bytes + 2) * service_multiplication_factor
    assert tr.sample_statistics(
    ).in_frames == 3 * service_multiplication_factor
    assert tr.sample_statistics().in_out_of_band_bytes == 0
    assert tr.sample_statistics().out_bytes == tr.sample_statistics().in_bytes
    assert tr.sample_statistics(
    ).out_frames == 3 * service_multiplication_factor
    assert tr.sample_statistics(
    ).out_transfers == 1 * service_multiplication_factor
    assert tr.sample_statistics().out_incomplete == 0

    #
    # Write timeout test.
    #
    assert not await broadcaster.send(
        Transfer(timestamp=Timestamp.now(),
                 priority=Priority.IMMEDIATE,
                 transfer_id=99999,
                 fragmented_payload=payload_x3),
        monotonic_deadline=get_monotonic() -
        5.0,  # The deadline is in the past.
    )

    assert None is await subscriber_selective.receive(get_monotonic() + 0.1)
    assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1)
    assert None is await server_listener.receive(get_monotonic() + 0.1)
    assert None is await client_listener.receive(get_monotonic() + 0.1)

    print(tr.sample_statistics())
    assert tr.sample_statistics().in_bytes >= (
        32 * 3 + payload_x3_size_bytes + 2) * service_multiplication_factor
    assert tr.sample_statistics(
    ).in_frames == 3 * service_multiplication_factor
    assert tr.sample_statistics().in_out_of_band_bytes == 0
    assert tr.sample_statistics().out_bytes == tr.sample_statistics().in_bytes
    assert tr.sample_statistics(
    ).out_frames == 3 * service_multiplication_factor
    assert tr.sample_statistics(
    ).out_transfers == 1 * service_multiplication_factor
    assert tr.sample_statistics().out_incomplete == 1  # INCREMENTED HERE

    #
    # Selective message exchange test.
    #
    assert await broadcaster.send(
        Transfer(timestamp=Timestamp.now(),
                 priority=Priority.IMMEDIATE,
                 transfer_id=99999,
                 fragmented_payload=payload_x3),
        monotonic_deadline=get_monotonic() + 5.0,
    )

    rx_transfer = await subscriber_promiscuous.receive(get_monotonic() + 5.0)
    print("PROMISCUOUS SUBSCRIBER TRANSFER:", rx_transfer)
    assert isinstance(rx_transfer, TransferFrom)
    assert rx_transfer.priority == Priority.IMMEDIATE
    assert rx_transfer.transfer_id == 99999
    assert b"".join(rx_transfer.fragmented_payload) == b"".join(payload_x3)

    rx_transfer = await subscriber_selective.receive(get_monotonic() + 1.0)
    print("SELECTIVE SUBSCRIBER TRANSFER:", rx_transfer)
    assert isinstance(rx_transfer, TransferFrom)
    assert rx_transfer.priority == Priority.IMMEDIATE
    assert rx_transfer.transfer_id == 99999
    assert b"".join(rx_transfer.fragmented_payload) == b"".join(payload_x3)

    assert None is await subscriber_selective.receive(get_monotonic() + 0.1)
    assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1)
    assert None is await server_listener.receive(get_monotonic() + 0.1)
    assert None is await client_listener.receive(get_monotonic() + 0.1)

    #
    # Out-of-band data test.
    #
    with caplog.at_level(logging.CRITICAL,
                         logger=pyuavcan.transport.serial.__name__):
        stats_reference = tr.sample_statistics()

        # The frame delimiter is needed to force new frame into the state machine.
        grownups = b"Aren't there any grownups at all? - No grownups!\x00"
        tr.serial_port.write(grownups)
        stats_reference.in_bytes += len(grownups)
        stats_reference.in_out_of_band_bytes += len(grownups)

        # Wait for the reader thread to catch up.
        assert None is await subscriber_selective.receive(get_monotonic() +
                                                          0.2)
        assert None is await subscriber_promiscuous.receive(get_monotonic() +
                                                            0.2)
        assert None is await server_listener.receive(get_monotonic() + 0.2)
        assert None is await client_listener.receive(get_monotonic() + 0.2)

        print(tr.sample_statistics())
        assert tr.sample_statistics() == stats_reference

        # The frame delimiter is needed to force new frame into the state machine.
        tr.serial_port.write(
            bytes([0xFF, 0xFF, SerialFrame.FRAME_DELIMITER_BYTE]))
        stats_reference.in_bytes += 3
        stats_reference.in_out_of_band_bytes += 3

        # Wait for the reader thread to catch up.
        assert None is await subscriber_selective.receive(get_monotonic() +
                                                          0.2)
        assert None is await subscriber_promiscuous.receive(get_monotonic() +
                                                            0.2)
        assert None is await server_listener.receive(get_monotonic() + 0.2)
        assert None is await client_listener.receive(get_monotonic() + 0.2)

        print(tr.sample_statistics())
        assert tr.sample_statistics() == stats_reference

    #
    # Termination.
    #
    assert set(tr.input_sessions) == {
        subscriber_promiscuous, subscriber_selective, server_listener,
        client_listener
    }
    assert set(tr.output_sessions) == {
        broadcaster, server_responder, client_requester
    }

    subscriber_promiscuous.close()
    subscriber_promiscuous.close()  # Idempotency.

    assert set(tr.input_sessions) == {
        subscriber_selective, server_listener, client_listener
    }
    assert set(tr.output_sessions) == {
        broadcaster, server_responder, client_requester
    }

    broadcaster.close()
    broadcaster.close()  # Idempotency.

    assert set(tr.input_sessions) == {
        subscriber_selective, server_listener, client_listener
    }
    assert set(tr.output_sessions) == {server_responder, client_requester}

    tr.close()
    tr.close()  # Idempotency.

    assert not set(tr.input_sessions)
    assert not set(tr.output_sessions)

    with pytest.raises(pyuavcan.transport.ResourceClosedError):
        _ = tr.get_output_session(
            OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    with pytest.raises(pyuavcan.transport.ResourceClosedError):
        _ = tr.get_input_session(
            InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    await asyncio.sleep(
        1
    )  # Let all pending tasks finalize properly to avoid stack traces in the output.
Beispiel #12
0
async def _unittest_udp_transport_ipv4() -> None:
    from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom
    from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier
    from pyuavcan.transport import ProtocolParameters

    asyncio.get_running_loop().slow_callback_duration = 5.0

    get_monotonic = asyncio.get_event_loop().time

    with pytest.raises(ValueError):
        _ = UDPTransport("127.0.0.111", mtu=10)

    with pytest.raises(ValueError):
        _ = UDPTransport("127.0.0.111", service_transfer_multiplier=100)

    tr = UDPTransport("127.0.0.111", mtu=9000)
    tr2 = UDPTransport("127.0.0.222", service_transfer_multiplier=2)

    assert tr.local_ip_address == ipaddress.ip_address("127.0.0.111")
    assert tr2.local_ip_address == ipaddress.ip_address("127.0.0.222")

    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 "127.0.0.111" in repr(tr)
    assert tr.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2 ** 64,
        max_nodes=65535,
        mtu=9000,
    )

    default_mtu = min(UDPTransport.VALID_MTU_RANGE)
    assert "127.0.0.222" in repr(tr2)
    assert tr2.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2 ** 64,
        max_nodes=65535,
        mtu=default_mtu,
    )

    assert tr.sample_statistics() == tr2.sample_statistics() == UDPTransportStatistics()

    payload_single = [_mem("qwertyui"), _mem("01234567")] * (default_mtu // 16)
    assert sum(map(len, payload_single)) == default_mtu

    payload_x3 = (payload_single * 3)[:-1]
    payload_x3_size_bytes = default_mtu * 3 - 8
    assert sum(map(len, payload_x3)) == payload_x3_size_bytes

    #
    # Instantiate session objects.
    #
    meta = PayloadMetadata(10000)

    broadcaster = tr2.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    assert broadcaster is tr2.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    subscriber_promiscuous = tr.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    assert subscriber_promiscuous is tr.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    subscriber_selective = tr.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), 123), meta)
    assert subscriber_selective is tr.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), 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().received_datagrams[MessageDataSpecifier(2345)].accepted_datagrams == {}
    assert (
        tr.sample_statistics()
        .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST)]
        .accepted_datagrams
        == {}
    )

    assert (
        tr2.sample_statistics()
        .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE)]
        .accepted_datagrams
        == {}
    )

    #
    # Message exchange test.
    #
    assert await broadcaster.send(
        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(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)]

    print("tr :", tr.sample_statistics())
    assert tr.sample_statistics().received_datagrams[MessageDataSpecifier(2345)].accepted_datagrams == {222: 1}
    assert (
        tr.sample_statistics()
        .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST)]
        .accepted_datagrams
        == {}
    )
    print("tr2:", tr2.sample_statistics())
    assert (
        tr2.sample_statistics()
        .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE)]
        .accepted_datagrams
        == {}
    )

    assert None is await subscriber_selective.receive(get_monotonic() + 0.1)
    assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1)
    assert None is await server_listener.receive(get_monotonic() + 0.1)
    assert None is await client_listener.receive(get_monotonic() + 0.1)

    #
    # Service exchange test.
    #
    assert await client_requester.send(
        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(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(get_monotonic() + 0.1)
    assert None is await subscriber_promiscuous.receive(get_monotonic() + 0.1)
    assert None is await server_listener.receive(get_monotonic() + 0.1)
    assert None is await client_listener.receive(get_monotonic() + 0.1)

    print("tr :", tr.sample_statistics())
    assert tr.sample_statistics().received_datagrams[MessageDataSpecifier(2345)].accepted_datagrams == {222: 1}
    assert tr.sample_statistics().received_datagrams[
        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()
        .received_datagrams[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(2345), None), meta)

    with pytest.raises(pyuavcan.transport.ResourceClosedError):
        _ = tr2.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    await asyncio.sleep(1)  # Let all pending tasks finalize properly to avoid stack traces in the output.
Beispiel #13
0
async def _unittest_can_capture_trace() -> None:
    from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer, Priority, Timestamp
    from pyuavcan.transport import InputSessionSpecifier, OutputSessionSpecifier, TransferTrace
    from .media.mock import MockMedia
    from pyuavcan.transport.can import CANCapture
    from pyuavcan.transport.can.media import FilterConfiguration, FrameFormat

    asyncio.get_running_loop().slow_callback_duration = 5.0

    ts = Timestamp.now()

    peers: typing.Set[MockMedia] = set()
    media = MockMedia(peers, 64, 2)
    media2 = MockMedia(peers, 64, 2)

    tr = can.CANTransport(media, None)
    tr2 = can.CANTransport(media2, 51)

    captures: typing.List[CANCapture] = []
    captures_other: typing.List[CANCapture] = []

    def add_capture(cap: pyuavcan.transport.Capture) -> None:
        assert isinstance(cap, CANCapture)
        captures.append(cap)

    def add_capture_other(cap: pyuavcan.transport.Capture) -> None:
        assert isinstance(cap, CANCapture)
        captures_other.append(cap)

    tr.begin_capture(add_capture)
    tr.begin_capture(add_capture_other)
    assert media.acceptance_filters == [
        FilterConfiguration.new_promiscuous(FrameFormat.BASE),
        FilterConfiguration.new_promiscuous(FrameFormat.EXTENDED),
    ]

    a_out = tr.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(2345), None), PayloadMetadata(800))
    b_out = tr2.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(5432), None), PayloadMetadata(800))

    # Ensure the filter configuration is not reset when creating new subscriptions.
    a_in = tr2.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), None), PayloadMetadata(800))
    assert media.acceptance_filters == [
        FilterConfiguration.new_promiscuous(FrameFormat.BASE),
        FilterConfiguration.new_promiscuous(FrameFormat.EXTENDED),
    ]

    # Send transfers to collect some captures.
    assert await a_out.send(
        Transfer(ts, Priority.NOMINAL, transfer_id=11, fragmented_payload=[memoryview(b"first")]),
        monotonic_deadline=tr.loop.time() + 2.0,
    )
    await asyncio.sleep(1.0)  # Let messages propagate.
    assert await b_out.send(
        Transfer(ts, Priority.NOMINAL, transfer_id=22, fragmented_payload=[memoryview(b"second")]),
        monotonic_deadline=tr.loop.time() + 2.0,
    )
    transfer = await a_in.receive(tr.loop.time() + 2.0)
    assert transfer
    assert transfer.transfer_id == 11
    await asyncio.sleep(1.0)  # Let messages propagate.

    # Validate the captures.
    assert captures == captures_other
    assert len(captures) == 2  # One sent, one received.
    assert captures[0].own
    assert b"first" in captures[0].frame.data
    assert not captures[1].own
    assert b"second" in captures[1].frame.data

    # Check the loopback stats.
    assert tr.sample_statistics().in_frames == 1
    assert tr.sample_statistics().in_frames_loopback == 1
    assert tr2.sample_statistics().in_frames == 1
    assert tr2.sample_statistics().in_frames_loopback == 0

    # Perform basic tracer test (the full test is implemented separately).
    tracer = tr.make_tracer()
    trc = tracer.update(captures[0])
    assert isinstance(trc, TransferTrace)
    assert b"first" in trc.transfer.fragmented_payload[0].tobytes()
    trc = tracer.update(captures[1])
    assert isinstance(trc, TransferTrace)
    assert b"second" in trc.transfer.fragmented_payload[0].tobytes()