コード例 #1
0
ファイル: _can.py プロジェクト: alkasm/pyuavcan
async def _unittest_issue_120() -> None:
    from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer
    from pyuavcan.transport import Priority, Timestamp, OutputSessionSpecifier
    from .media.mock import MockMedia

    asyncio.get_running_loop().slow_callback_duration = 5.0

    peers: typing.Set[MockMedia] = set()
    media = MockMedia(peers, 8, 10)
    tr = can.CANTransport(media, 42)
    assert tr.protocol_parameters.transfer_id_modulo == 32

    feedback_collector = _FeedbackCollector()

    ses = tr.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None),
        PayloadMetadata(1024))
    ses.enable_feedback(feedback_collector.give)
    for i in range(70):
        ts = Timestamp.now()
        assert await ses.send(
            Transfer(
                timestamp=ts,
                priority=Priority.SLOW,
                transfer_id=i,
                fragmented_payload=[_mem(str(i))] *
                7,  # Ensure both single- and multiframe
            ),
            tr.loop.time() + 1.0,
        )
        await asyncio.sleep(0.1)
        fb = feedback_collector.take()
        assert fb.original_transfer_timestamp == ts

    num_frames = (10 * 1) + (60 * 3)  # 10 single-frame, 60 multi-frame
    assert 70 == ses.sample_statistics().transfers
    assert num_frames == ses.sample_statistics().frames
    assert 0 == tr.sample_statistics().in_frames  # loopback not included here
    assert 70 == tr.sample_statistics(
    ).in_frames_loopback  # only first frame of each transfer
    assert num_frames == tr.sample_statistics().out_frames
    assert 70 == tr.sample_statistics(
    ).out_frames_loopback  # only first frame of each transfer
    assert 0 == tr.sample_statistics().lost_loopback_frames
コード例 #2
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()
コード例 #3
0
ファイル: _output.py プロジェクト: flytrex-vadim/pyuavcan
def _unittest_output_session() -> None:
    from pytest import raises
    from pyuavcan.transport import OutputSessionSpecifier, MessageDataSpecifier, ServiceDataSpecifier, Priority
    from pyuavcan.transport import PayloadMetadata, SessionStatistics, Timestamp, Feedback, Transfer

    ts = Timestamp.now()
    loop = asyncio.get_event_loop()
    run_until_complete = loop.run_until_complete
    finalized = False

    def do_finalize() -> None:
        nonlocal finalized
        finalized = True

    def check_timestamp(t: pyuavcan.transport.Timestamp) -> bool:
        now = pyuavcan.transport.Timestamp.now()
        s = ts.system_ns <= t.system_ns <= now.system_ns
        m = ts.monotonic_ns <= t.monotonic_ns <= now.system_ns
        return s and m

    destination_endpoint = '127.100.0.1', 25406

    sock_rx = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM)
    sock_rx.bind(destination_endpoint)
    sock_rx.settimeout(1.0)

    def make_sock() -> socket_.socket:
        sock = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM)
        sock.bind(('127.100.0.2', 0))
        sock.connect(destination_endpoint)
        sock.setblocking(False)
        return sock

    sos = UDPOutputSession(
        specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None),
        payload_metadata=PayloadMetadata(0xdead_beef_badc0ffe, 1024),
        mtu=11,
        multiplier=1,
        sock=make_sock(),
        loop=asyncio.get_event_loop(),
        finalizer=do_finalize,
    )

    assert sos.specifier == OutputSessionSpecifier(MessageDataSpecifier(3210),
                                                   None)
    assert sos.destination_node_id is None
    assert sos.payload_metadata == PayloadMetadata(0xdead_beef_badc0ffe, 1024)
    assert sos.sample_statistics() == SessionStatistics()

    assert run_until_complete(
        sos.send_until(
            Transfer(timestamp=ts,
                     priority=Priority.NOMINAL,
                     transfer_id=12340,
                     fragmented_payload=[
                         memoryview(b'one'),
                         memoryview(b'two'),
                         memoryview(b'three')
                     ]),
            loop.time() + 10.0))

    rx_data, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == '127.100.0.2'
    assert rx_data == (
        b'\x00\x04\x00\x00\x00\x00\x00\x8040\x00\x00\x00\x00\x00\x00\xfe\x0f\xdc\xba\xef\xbe\xad\xde'
        + b'one'
        b'two'
        b'three')
    with raises(socket_.timeout):
        sock_rx.recvfrom(1000)

    last_feedback: typing.Optional[Feedback] = None

    def feedback_handler(feedback: Feedback) -> None:
        nonlocal last_feedback
        last_feedback = feedback

    sos.enable_feedback(feedback_handler)

    assert last_feedback is None
    assert run_until_complete(
        sos.send_until(
            Transfer(timestamp=ts,
                     priority=Priority.NOMINAL,
                     transfer_id=12340,
                     fragmented_payload=[]),
            loop.time() + 10.0))
    assert last_feedback is not None
    assert last_feedback.original_transfer_timestamp == ts
    assert check_timestamp(last_feedback.first_frame_transmission_timestamp)

    sos.disable_feedback()
    sos.disable_feedback()  # Idempotency check

    _, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == '127.100.0.2'
    with raises(socket_.timeout):
        sock_rx.recvfrom(1000)

    assert sos.sample_statistics() == SessionStatistics(transfers=2,
                                                        frames=2,
                                                        payload_bytes=11,
                                                        errors=0,
                                                        drops=0)

    assert sos.socket.fileno() >= 0
    assert not finalized
    sos.close()
    assert finalized
    assert sos.socket.fileno() < 0  # The socket is supposed to be disposed of.
    finalized = False

    # Multi-frame with multiplication
    sos = UDPOutputSession(
        specifier=OutputSessionSpecifier(
            ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST),
            2222),
        payload_metadata=PayloadMetadata(0xdead_beef_badc0ffe, 1024),
        mtu=10,
        multiplier=2,
        sock=make_sock(),
        loop=asyncio.get_event_loop(),
        finalizer=do_finalize,
    )
    assert run_until_complete(
        sos.send_until(
            Transfer(timestamp=ts,
                     priority=Priority.OPTIONAL,
                     transfer_id=54321,
                     fragmented_payload=[
                         memoryview(b'one'),
                         memoryview(b'two'),
                         memoryview(b'three')
                     ]),
            loop.time() + 10.0))
    data_main_a, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == '127.100.0.2'
    data_main_b, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == '127.100.0.2'
    data_redundant_a, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == '127.100.0.2'
    data_redundant_b, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == '127.100.0.2'
    with raises(socket_.timeout):
        sock_rx.recvfrom(1000)

    print('data_main_a', data_main_a)
    print('data_main_b', data_main_b)
    print('data_redundant_a', data_redundant_a)
    print('data_redundant_b', data_redundant_b)

    assert data_main_a == data_redundant_a
    assert data_main_b == data_redundant_b
    assert data_main_a == (
        b'\x00\x07\x00\x00\x00\x00\x00\x001\xd4\x00\x00\x00\x00\x00\x00\xfe\x0f\xdc\xba\xef\xbe\xad\xde'
        + b'one'
        b'two'
        b'three'[:-1])
    assert data_main_b == (
        b'\x00\x07\x00\x00\x01\x00\x00\x801\xd4\x00\x00\x00\x00\x00\x00\xfe\x0f\xdc\xba\xef\xbe\xad\xde'
        + b'e' + pyuavcan.transport.commons.crc.CRC32C.new(
            b'one', b'two', b'three').value_as_bytes)

    sos = UDPOutputSession(
        specifier=OutputSessionSpecifier(
            ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST),
            2222),
        payload_metadata=PayloadMetadata(0xdead_beef_badc0ffe, 1024),
        mtu=10,
        multiplier=1,
        sock=make_sock(),
        loop=asyncio.get_event_loop(),
        finalizer=do_finalize,
    )

    # Induced timeout
    assert not run_until_complete(
        sos.send_until(
            Transfer(timestamp=ts,
                     priority=Priority.NOMINAL,
                     transfer_id=12340,
                     fragmented_payload=[
                         memoryview(b'one'),
                         memoryview(b'two'),
                         memoryview(b'three')
                     ]),
            loop.time() - 0.1  # Expired on arrival
        ))

    assert sos.sample_statistics() == SessionStatistics(
        transfers=0,
        frames=0,
        payload_bytes=0,
        errors=0,
        drops=2  # Because multiframe
    )

    # Induced failure
    sos.socket.close()
    with raises(OSError):
        assert not run_until_complete(
            sos.send_until(
                Transfer(timestamp=ts,
                         priority=Priority.NOMINAL,
                         transfer_id=12340,
                         fragmented_payload=[
                             memoryview(b'one'),
                             memoryview(b'two'),
                             memoryview(b'three')
                         ]),
                loop.time() + 10.0))

    assert sos.sample_statistics() == SessionStatistics(transfers=0,
                                                        frames=0,
                                                        payload_bytes=0,
                                                        errors=1,
                                                        drops=2)

    assert not finalized
    sos.close()
    assert finalized
    sos.close()  # Idempotency

    with raises(pyuavcan.transport.ResourceClosedError):
        run_until_complete(
            sos.send_until(
                Transfer(timestamp=ts,
                         priority=Priority.NOMINAL,
                         transfer_id=12340,
                         fragmented_payload=[
                             memoryview(b'one'),
                             memoryview(b'two'),
                             memoryview(b'three')
                         ]),
                loop.time() + 10.0))

    sock_rx.close()
コード例 #4
0
ファイル: _udp.py プロジェクト: manuksh/pyuavcan
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)
コード例 #5
0
async def _unittest_output_session() -> None:
    ts = Timestamp.now()
    loop = asyncio.get_event_loop()

    tx_timestamp: typing.Optional[Timestamp] = Timestamp.now()
    tx_exception: typing.Optional[Exception] = None
    last_sent_frames: typing.List[SerialFrame] = []
    last_monotonic_deadline = 0.0
    finalized = False

    async def do_send(frames: typing.Sequence[SerialFrame],
                      monotonic_deadline: float) -> typing.Optional[Timestamp]:
        nonlocal last_sent_frames
        nonlocal last_monotonic_deadline
        last_sent_frames = list(frames)
        last_monotonic_deadline = monotonic_deadline
        if tx_exception:
            raise tx_exception
        return tx_timestamp

    def do_finalize() -> None:
        nonlocal finalized
        finalized = True

    with raises(pyuavcan.transport.OperationNotDefinedForAnonymousNodeError):
        SerialOutputSession(
            specifier=OutputSessionSpecifier(
                ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST),
                1111),
            payload_metadata=PayloadMetadata(1024),
            mtu=10,
            local_node_id=None,
            send_handler=do_send,
            finalizer=do_finalize,
        )

    sos = SerialOutputSession(
        specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None),
        payload_metadata=PayloadMetadata(1024),
        mtu=11,
        local_node_id=None,
        send_handler=do_send,
        finalizer=do_finalize,
    )

    assert sos.specifier == OutputSessionSpecifier(MessageDataSpecifier(3210),
                                                   None)
    assert sos.destination_node_id is None
    assert sos.payload_metadata == PayloadMetadata(1024)
    assert sos.sample_statistics() == SessionStatistics()

    assert await (sos.send(
        Transfer(
            timestamp=ts,
            priority=Priority.NOMINAL,
            transfer_id=12340,
            fragmented_payload=[
                memoryview(b"one"),
                memoryview(b"two"),
                memoryview(b"three")
            ],
        ),
        999999999.999,
    ))
    assert last_monotonic_deadline == approx(999999999.999)
    assert len(last_sent_frames) == 1

    with raises(pyuavcan.transport.OperationNotDefinedForAnonymousNodeError):
        await (sos.send(
            Transfer(
                timestamp=ts,
                priority=Priority.NOMINAL,
                transfer_id=12340,
                fragmented_payload=[
                    memoryview(b"one"),
                    memoryview(b"two"),
                    memoryview(b"three four five")
                ],
            ),
            loop.time() + 10.0,
        ))

    last_feedback: typing.Optional[Feedback] = None

    def feedback_handler(feedback: Feedback) -> None:
        nonlocal last_feedback
        last_feedback = feedback

    sos.enable_feedback(feedback_handler)

    assert last_feedback is None
    assert await (sos.send(
        Transfer(timestamp=ts,
                 priority=Priority.NOMINAL,
                 transfer_id=12340,
                 fragmented_payload=[]), 999999999.999))
    assert last_monotonic_deadline == approx(999999999.999)
    assert len(last_sent_frames) == 1
    assert last_feedback is not None
    assert last_feedback.original_transfer_timestamp == ts
    assert last_feedback.first_frame_transmission_timestamp == tx_timestamp

    sos.disable_feedback()
    sos.disable_feedback()  # Idempotency check

    assert sos.sample_statistics() == SessionStatistics(transfers=2,
                                                        frames=2,
                                                        payload_bytes=11,
                                                        errors=0,
                                                        drops=0)

    assert not finalized
    sos.close()
    assert finalized
    finalized = False

    sos = SerialOutputSession(
        specifier=OutputSessionSpecifier(
            ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST),
            2222),
        payload_metadata=PayloadMetadata(1024),
        mtu=10,
        local_node_id=1234,
        send_handler=do_send,
        finalizer=do_finalize,
    )

    # Induced failure
    tx_timestamp = None
    assert not await (sos.send(
        Transfer(
            timestamp=ts,
            priority=Priority.NOMINAL,
            transfer_id=12340,
            fragmented_payload=[
                memoryview(b"one"),
                memoryview(b"two"),
                memoryview(b"three")
            ],
        ),
        999999999.999,
    ))
    assert last_monotonic_deadline == approx(999999999.999)
    assert len(last_sent_frames) == 2

    assert sos.sample_statistics() == SessionStatistics(transfers=0,
                                                        frames=0,
                                                        payload_bytes=0,
                                                        errors=0,
                                                        drops=2)

    tx_exception = RuntimeError()
    with raises(RuntimeError):
        _ = await (sos.send(
            Transfer(
                timestamp=ts,
                priority=Priority.NOMINAL,
                transfer_id=12340,
                fragmented_payload=[
                    memoryview(b"one"),
                    memoryview(b"two"),
                    memoryview(b"three")
                ],
            ),
            loop.time() + 10.0,
        ))

    assert sos.sample_statistics() == SessionStatistics(transfers=0,
                                                        frames=0,
                                                        payload_bytes=0,
                                                        errors=1,
                                                        drops=2)

    assert not finalized
    sos.close()
    assert finalized
    sos.close()  # Idempotency

    with raises(pyuavcan.transport.ResourceClosedError):
        await (sos.send(
            Transfer(
                timestamp=ts,
                priority=Priority.NOMINAL,
                transfer_id=12340,
                fragmented_payload=[
                    memoryview(b"one"),
                    memoryview(b"two"),
                    memoryview(b"three")
                ],
            ),
            loop.time() + 10.0,
        ))
コード例 #6
0
async def _unittest_output_session_no_listener() -> None:
    """
    Test the Windows-specific corner case. Should be handled identically on all platforms.
    """
    ts = Timestamp.now()
    loop = asyncio.get_event_loop()
    loop.slow_callback_duration = 5.0

    def make_sock() -> socket_.socket:
        sock = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM)
        sock.bind(("127.100.0.2", 0))
        sock.connect(
            ("239.0.1.2", 33333))  # There is no listener on this endpoint.
        sock.setsockopt(socket_.IPPROTO_IP, socket_.IP_MULTICAST_IF,
                        socket_.inet_aton("127.100.0.2"))
        sock.setblocking(False)
        return sock

    sos = UDPOutputSession(
        specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None),
        payload_metadata=PayloadMetadata(1024),
        mtu=11,
        multiplier=1,
        sock=make_sock(),
        finalizer=lambda: None,
    )
    assert await (sos.send(
        Transfer(
            timestamp=ts,
            priority=Priority.NOMINAL,
            transfer_id=12340,
            fragmented_payload=[
                memoryview(b"one"),
                memoryview(b"two"),
                memoryview(b"three")
            ],
        ),
        loop.time() + 10.0,
    ))
    sos.close()

    # Multi-frame with multiplication and feedback
    last_feedback: typing.Optional[Feedback] = None

    def feedback_handler(feedback: Feedback) -> None:
        nonlocal last_feedback
        assert last_feedback is None, "Unexpected feedback"
        last_feedback = feedback

    sos = UDPOutputSession(
        specifier=OutputSessionSpecifier(
            ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST),
            2222),
        payload_metadata=PayloadMetadata(1024),
        mtu=10,
        multiplier=2,
        sock=make_sock(),
        finalizer=lambda: None,
    )
    sos.enable_feedback(feedback_handler)
    assert last_feedback is None
    assert await (sos.send(
        Transfer(
            timestamp=ts,
            priority=Priority.OPTIONAL,
            transfer_id=54321,
            fragmented_payload=[
                memoryview(b"one"),
                memoryview(b"two"),
                memoryview(b"three")
            ],
        ),
        loop.time() + 10.0,
    ))
    print("last_feedback:", last_feedback)
    assert isinstance(last_feedback, UDPFeedback)
    # Ensure that the timestamp is populated even if the error suppression logic is activated.
    assert last_feedback.original_transfer_timestamp == ts
    assert Timestamp.now(
    ).monotonic >= last_feedback.first_frame_transmission_timestamp.monotonic >= ts.monotonic
    assert Timestamp.now(
    ).system >= last_feedback.first_frame_transmission_timestamp.system >= ts.system

    sos.close()
コード例 #7
0
ファイル: _can.py プロジェクト: wiboticalex/pyuavcan
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()
コード例 #8
0
def _unittest_redundant_input_cyclic() -> None:
    import time
    import pytest
    from pyuavcan.transport import Transfer, Timestamp, Priority, ResourceClosedError
    from pyuavcan.transport.loopback import LoopbackTransport

    loop = asyncio.get_event_loop()
    await_ = loop.run_until_complete

    spec = pyuavcan.transport.InputSessionSpecifier(
        pyuavcan.transport.MessageDataSpecifier(4321), None)
    spec_tx = pyuavcan.transport.OutputSessionSpecifier(
        spec.data_specifier, None)
    meta = pyuavcan.transport.PayloadMetadata(30)

    ts = Timestamp.now()

    tr_a = LoopbackTransport(111)
    tr_b = LoopbackTransport(111)
    tx_a = tr_a.get_output_session(spec_tx, meta)
    tx_b = tr_b.get_output_session(spec_tx, meta)
    inf_a = tr_a.get_input_session(spec, meta)
    inf_b = tr_b.get_input_session(spec, meta)

    inf_a.transfer_id_timeout = 1.1  # This is used to ensure that the transfer-ID timeout is handled correctly.

    is_retired = False

    def retire() -> None:
        nonlocal is_retired
        is_retired = True

    ses = RedundantInputSession(
        spec,
        meta,
        tid_modulo_provider=lambda: 32,
        loop=loop,
        finalizer=retire  # Like CAN, for example.
    )
    assert not is_retired
    assert ses.specifier is spec
    assert ses.payload_metadata is meta
    assert not ses.inferiors
    assert ses.sample_statistics() == RedundantSessionStatistics()
    assert pytest.approx(0.0) == ses.transfer_id_timeout

    # Empty inferior set reception.
    time_before = loop.time()
    assert not await_(ses.receive(loop.time() + 2.0))
    assert 1.0 < loop.time(
    ) - time_before < 5.0, "The method should have returned in about two seconds."

    # Begin reception, then add an inferior while the reception is in progress.
    assert await_(
        tx_a.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=1,
                fragmented_payload=[memoryview(b"abc")],
            ),
            loop.time() + 1.0,
        ))

    async def add_inferior(inferior: pyuavcan.transport.InputSession) -> None:
        await asyncio.sleep(1.0)
        ses._add_inferior(inferior)  # pylint: disable=protected-access

    time_before = loop.time()
    tr, _ = await_(
        asyncio.gather(
            # Start reception here. It would stall for two seconds because no inferiors.
            ses.receive(loop.time() + 2.0),
            # While the transmission is stalled, add one inferior with a delay.
            add_inferior(inf_a),
        ))
    assert 0.0 < loop.time(
    ) - time_before < 5.0, "The method should have returned in about one second."
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 1
    assert tr.fragmented_payload == [memoryview(b"abc")]
    assert tr.inferior_session == inf_a

    # More inferiors
    assert ses.transfer_id_timeout == pytest.approx(1.1)
    ses._add_inferior(inf_a)  # No change, added above    # pylint: disable=protected-access
    assert ses.inferiors == [inf_a]
    ses._add_inferior(inf_b)  # pylint: disable=protected-access
    assert ses.inferiors == [inf_a, inf_b]
    assert ses.transfer_id_timeout == pytest.approx(1.1)
    assert inf_b.transfer_id_timeout == pytest.approx(1.1)

    # Redundant reception - new transfers accepted because the iface switch timeout is exceeded.
    time.sleep(ses.transfer_id_timeout
               )  # Just to make sure that it is REALLY exceeded.
    assert await_(
        tx_b.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=2,
                fragmented_payload=[memoryview(b"def")],
            ),
            loop.time() + 1.0,
        ))
    assert await_(
        tx_b.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=3,
                fragmented_payload=[memoryview(b"ghi")],
            ),
            loop.time() + 1.0,
        ))

    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 2
    assert tr.fragmented_payload == [memoryview(b"def")]
    assert tr.inferior_session == inf_b

    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 3
    assert tr.fragmented_payload == [memoryview(b"ghi")]
    assert tr.inferior_session == inf_b

    assert None is await_(
        ses.receive(loop.time() + 1.0))  # Nothing left to read now.

    # This one will be rejected because wrong iface and the switch timeout is not yet exceeded.
    assert await_(
        tx_a.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=4,
                fragmented_payload=[memoryview(b"rej")],
            ),
            loop.time() + 1.0,
        ))
    assert None is await_(ses.receive(loop.time() + 0.1))

    # Transfer-ID timeout reconfiguration.
    ses.transfer_id_timeout = 3.0
    with pytest.raises(ValueError):
        ses.transfer_id_timeout = -0.0
    assert ses.transfer_id_timeout == pytest.approx(3.0)
    assert inf_a.transfer_id_timeout == pytest.approx(3.0)
    assert inf_a.transfer_id_timeout == pytest.approx(3.0)

    # Inferior removal resets the state of the deduplicator.
    ses._close_inferior(0)  # pylint: disable=protected-access
    ses._close_inferior(1)  # Out of range, no effect.  # pylint: disable=protected-access
    assert ses.inferiors == [inf_b]

    assert await_(
        tx_b.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=1,
                fragmented_payload=[memoryview(b"acc")],
            ),
            loop.time() + 1.0,
        ))
    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 1
    assert tr.fragmented_payload == [memoryview(b"acc")]
    assert tr.inferior_session == inf_b

    # Stats check.
    assert ses.sample_statistics() == RedundantSessionStatistics(
        transfers=4,
        frames=inf_b.sample_statistics().frames,
        payload_bytes=12,
        errors=0,
        drops=0,
        inferiors=[
            inf_b.sample_statistics(),
        ],
    )

    # Closure.
    assert not is_retired
    ses.close()
    assert is_retired
    is_retired = False
    ses.close()
    assert not is_retired
    assert not ses.inferiors
    with pytest.raises(ResourceClosedError):
        await_(ses.receive(0))
コード例 #9
0
ファイル: _serial.py プロジェクト: thirtytwobits/pyuavcan
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.
コード例 #10
0
ファイル: _udp.py プロジェクト: wiboticalex/pyuavcan
async def _unittest_udp_transport_ipv4_capture() -> None:
    import socket
    from pyuavcan.transport.udp import UDPCapture, IPPacket
    from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer
    from pyuavcan.transport import Priority, Timestamp, OutputSessionSpecifier
    from pyuavcan.transport import Capture, AlienSessionSpecifier

    asyncio.get_running_loop().slow_callback_duration = 5.0

    tr_capture = UDPTransport("127.50.0.2", local_node_id=None)
    captures: typing.List[UDPCapture] = []

    def inhale(s: Capture) -> None:
        print("CAPTURED:", s)
        assert isinstance(s, UDPCapture)
        captures.append(s)

    tr_capture.begin_capture(inhale)
    await asyncio.sleep(1.0)

    tr = UDPTransport("127.50.0.111")
    meta = PayloadMetadata(10000)
    broadcaster = tr.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(190), None), meta)
    assert broadcaster is tr.get_output_session(OutputSessionSpecifier(MessageDataSpecifier(190), None), meta)

    # For reasons of Windows compatibility, we have to set up a dummy listener on the target multicast group.
    # Otherwise, we will not see any packets at all. This is Windows-specific.
    sink = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    sink.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sink.bind(("", 11111))
    sink.setsockopt(
        socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton("239.50.0.190") + socket.inet_aton("127.0.0.1")
    )

    ts = Timestamp.now()
    assert len(captures) == 0  # Assuming here that there are no other entities that might create noise.
    await broadcaster.send(
        Transfer(
            timestamp=ts,
            priority=Priority.NOMINAL,
            transfer_id=9876543210,
            fragmented_payload=[_mem(bytes(range(256)))] * 4,
        ),
        monotonic_deadline=tr.loop.time() + 2.0,
    )
    await asyncio.sleep(1.0)  # Let the packet propagate.
    assert len(captures) == 1  # Ensure the packet is captured.
    tr_capture.close()  # Ensure the capture is stopped after the capturing transport is closed.
    await broadcaster.send(  # This one shall be ignored.
        Transfer(timestamp=Timestamp.now(), priority=Priority.HIGH, transfer_id=54321, fragmented_payload=[_mem(b"")]),
        monotonic_deadline=tr.loop.time() + 2.0,
    )
    await asyncio.sleep(1.0)
    assert len(captures) == 1  # Ignored?
    tr.close()
    sink.close()

    (pkt,) = captures
    assert isinstance(pkt, UDPCapture)
    assert (ts.monotonic - 1) <= pkt.timestamp.monotonic <= Timestamp.now().monotonic
    assert (ts.system - 1) <= pkt.timestamp.system <= Timestamp.now().system
    ip_pkt = IPPacket.parse(pkt.link_layer_packet)
    assert ip_pkt is not None
    assert [str(x) for x in ip_pkt.source_destination] == ["127.50.0.111", "239.50.0.190"]
    parsed = pkt.parse()
    assert parsed
    ses, frame = parsed
    assert isinstance(ses, AlienSessionSpecifier)
    assert ses.source_node_id == 111
    assert ses.destination_node_id is None
    assert ses.data_specifier == broadcaster.specifier.data_specifier
    assert frame.end_of_transfer
    assert frame.index == 0
    assert frame.transfer_id == 9876543210
    assert len(frame.payload) == 1024
    assert frame.priority == Priority.NOMINAL
コード例 #11
0
ファイル: _udp.py プロジェクト: wiboticalex/pyuavcan
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.
コード例 #12
0
async def _unittest_redundant_output_exceptions(caplog: typing.Any) -> None:
    loop = asyncio.get_event_loop()

    spec = pyuavcan.transport.OutputSessionSpecifier(
        pyuavcan.transport.MessageDataSpecifier(4321), None)
    spec_rx = pyuavcan.transport.InputSessionSpecifier(spec.data_specifier,
                                                       None)
    meta = pyuavcan.transport.PayloadMetadata(30 * 1024 * 1024)

    ts = Timestamp.now()

    is_retired = False

    def retire() -> None:
        nonlocal is_retired
        is_retired = True

    ses = RedundantOutputSession(spec, meta, finalizer=retire)
    assert not is_retired
    assert ses.specifier is spec
    assert ses.payload_metadata is meta
    assert not ses.inferiors
    assert ses.sample_statistics() == RedundantSessionStatistics()

    tr_a = LoopbackTransport(111)
    tr_b = LoopbackTransport(111)
    inf_a = tr_a.get_output_session(spec, meta)
    inf_b = tr_b.get_output_session(spec, meta)
    rx_a = tr_a.get_input_session(spec_rx, meta)
    rx_b = tr_b.get_input_session(spec_rx, meta)
    ses._add_inferior(inf_a)  # pylint: disable=protected-access
    ses._add_inferior(inf_b)  # pylint: disable=protected-access

    # Transmission with exceptions.
    # If at least one transmission succeeds, the call succeeds.
    with caplog.at_level(logging.CRITICAL, logger=__name__):
        inf_a.exception = RuntimeError("INTENDED EXCEPTION")
        assert await (ses.send(
            Transfer(
                timestamp=ts,
                priority=Priority.FAST,
                transfer_id=444444444444,
                fragmented_payload=[memoryview(b"INTENDED EXCEPTION")],
            ),
            loop.time() + 1.0,
        ))
        assert ses.sample_statistics() == RedundantSessionStatistics(
            transfers=1,
            frames=1,
            payload_bytes=len("INTENDED EXCEPTION"),
            errors=0,
            drops=0,
            inferiors=[
                SessionStatistics(
                    transfers=0,
                    frames=0,
                    payload_bytes=0,
                ),
                SessionStatistics(
                    transfers=1,
                    frames=1,
                    payload_bytes=len("INTENDED EXCEPTION"),
                ),
            ],
        )
        assert None is await (rx_a.receive(loop.time() + 1))
        tf_rx = await (rx_b.receive(loop.time() + 1))
        assert isinstance(tf_rx, TransferFrom)
        assert tf_rx.transfer_id == 444444444444
        assert tf_rx.fragmented_payload == [memoryview(b"INTENDED EXCEPTION")]

        # Transmission timeout.
        # One times out, one raises an exception --> the result is timeout.
        inf_b.should_timeout = True
        assert not await (ses.send(
            Transfer(
                timestamp=ts,
                priority=Priority.FAST,
                transfer_id=2222222222222,
                fragmented_payload=[memoryview(b"INTENDED EXCEPTION")],
            ),
            loop.time() + 1.0,
        ))
        assert ses.sample_statistics().transfers == 1
        assert ses.sample_statistics().payload_bytes == len(
            "INTENDED EXCEPTION")
        assert ses.sample_statistics().errors == 0
        assert ses.sample_statistics().drops == 1
        assert None is await (rx_a.receive(loop.time() + 1))
        assert None is await (rx_b.receive(loop.time() + 1))

        # Transmission with exceptions.
        # If all transmissions fail, the call fails.
        inf_b.exception = RuntimeError("INTENDED EXCEPTION")
        with pytest.raises(RuntimeError, match="INTENDED EXCEPTION"):
            assert await (ses.send(
                Transfer(
                    timestamp=ts,
                    priority=Priority.FAST,
                    transfer_id=3333333333333,
                    fragmented_payload=[memoryview(b"INTENDED EXCEPTION")],
                ),
                loop.time() + 1.0,
            ))
        assert ses.sample_statistics().transfers == 1
        assert ses.sample_statistics().payload_bytes == len(
            "INTENDED EXCEPTION")
        assert ses.sample_statistics().errors == 1
        assert ses.sample_statistics().drops == 1
        assert None is await (rx_a.receive(loop.time() + 1))
        assert None is await (rx_b.receive(loop.time() + 1))

    # Retirement.
    assert not is_retired
    ses.close()
    assert is_retired
    # Make sure the inferiors have been closed.
    assert not tr_a.output_sessions
    assert not tr_b.output_sessions
    # Idempotency.
    is_retired = False
    ses.close()
    assert not is_retired

    await asyncio.sleep(2.0)
コード例 #13
0
async def _unittest_redundant_output() -> None:
    loop = asyncio.get_event_loop()

    spec = pyuavcan.transport.OutputSessionSpecifier(
        pyuavcan.transport.MessageDataSpecifier(4321), None)
    spec_rx = pyuavcan.transport.InputSessionSpecifier(spec.data_specifier,
                                                       None)
    meta = pyuavcan.transport.PayloadMetadata(30 * 1024 * 1024)

    ts = Timestamp.now()

    is_retired = False

    def retire() -> None:
        nonlocal is_retired
        is_retired = True

    ses = RedundantOutputSession(spec, meta, finalizer=retire)
    assert not is_retired
    assert ses.specifier is spec
    assert ses.payload_metadata is meta
    assert not ses.inferiors
    assert ses.sample_statistics() == RedundantSessionStatistics()

    # Transmit with an empty set of inferiors.
    time_before = loop.time()
    assert not await (ses.send(
        Transfer(
            timestamp=ts,
            priority=Priority.IMMEDIATE,
            transfer_id=1234567890,
            fragmented_payload=[memoryview(b"abc")],
        ),
        loop.time() + 2.0,
    ))
    assert 1.0 < loop.time(
    ) - time_before < 5.0, "The method should have returned in about two seconds."
    assert ses.sample_statistics() == RedundantSessionStatistics(drops=1, )

    # Create inferiors.
    tr_a = LoopbackTransport(111)
    tr_b = LoopbackTransport(111)
    inf_a = tr_a.get_output_session(spec, meta)
    inf_b = tr_b.get_output_session(spec, meta)
    rx_a = tr_a.get_input_session(spec_rx, meta)
    rx_b = tr_b.get_input_session(spec_rx, meta)

    # Begin transmission, then add an inferior while it is in progress.
    async def add_inferior(inferior: pyuavcan.transport.OutputSession) -> None:
        print("sleeping before adding the inferior...")
        await asyncio.sleep(2.0)
        print("adding the inferior...")
        ses._add_inferior(inferior)  # pylint: disable=protected-access
        print("inferior has been added.")

    assert await (asyncio.gather(
        # Start transmission here. It would stall for up to five seconds because no inferiors.
        ses.send(
            Transfer(
                timestamp=ts,
                priority=Priority.IMMEDIATE,
                transfer_id=9876543210,
                fragmented_payload=[memoryview(b"def")],
            ),
            loop.time() + 5.0,
        ),
        # While the transmission is stalled, add one inferior with a 2-sec delay. It will unlock the stalled task.
        add_inferior(inf_a),
        # Then make sure that the transmission has actually taken place about after two seconds from the start.
    )), "Transmission should have succeeded"
    assert 1.0 < loop.time(
    ) - time_before < 5.0, "The method should have returned in about two seconds."
    assert ses.sample_statistics() == RedundantSessionStatistics(
        transfers=1,
        frames=1,
        payload_bytes=3,
        drops=1,
        inferiors=[
            SessionStatistics(
                transfers=1,
                frames=1,
                payload_bytes=3,
            ),
        ],
    )
    tf_rx = await (rx_a.receive(loop.time() + 1))
    assert isinstance(tf_rx, TransferFrom)
    assert tf_rx.transfer_id == 9876543210
    assert tf_rx.fragmented_payload == [memoryview(b"def")]
    assert None is await (rx_b.receive(loop.time() + 0.1))

    # Enable feedback.
    feedback: typing.List[RedundantFeedback] = []
    ses.enable_feedback(feedback.append)
    assert await (ses.send(
        Transfer(
            timestamp=ts,
            priority=Priority.LOW,
            transfer_id=555555555555,
            fragmented_payload=[memoryview(b"qwerty")],
        ),
        loop.time() + 1.0,
    ))
    assert ses.sample_statistics() == RedundantSessionStatistics(
        transfers=2,
        frames=2,
        payload_bytes=9,
        drops=1,
        inferiors=[
            SessionStatistics(
                transfers=2,
                frames=2,
                payload_bytes=9,
            ),
        ],
    )
    assert len(feedback) == 1
    assert feedback[0].inferior_session is inf_a
    assert feedback[0].original_transfer_timestamp == ts
    assert ts.system <= feedback[
        0].first_frame_transmission_timestamp.system <= time.time()
    assert ts.monotonic <= feedback[
        0].first_frame_transmission_timestamp.monotonic <= time.monotonic()
    assert isinstance(feedback[0].inferior_feedback, LoopbackFeedback)
    feedback.pop()
    assert not feedback
    tf_rx = await (rx_a.receive(loop.time() + 1))
    assert isinstance(tf_rx, TransferFrom)
    assert tf_rx.transfer_id == 555555555555
    assert tf_rx.fragmented_payload == [memoryview(b"qwerty")]
    assert None is await (rx_b.receive(loop.time() + 0.1))

    # Add a new inferior and ensure that its feedback is auto-enabled!
    ses._add_inferior(inf_b)  # pylint: disable=protected-access
    assert ses.inferiors == [
        inf_a,
        inf_b,
    ]
    # Double-add has no effect.
    ses._add_inferior(inf_b)  # pylint: disable=protected-access
    assert ses.inferiors == [
        inf_a,
        inf_b,
    ]
    assert await (ses.send(
        Transfer(
            timestamp=ts,
            priority=Priority.FAST,
            transfer_id=777777777777,
            fragmented_payload=[memoryview(b"fgsfds")],
        ),
        loop.time() + 1.0,
    ))
    assert ses.sample_statistics() == RedundantSessionStatistics(
        transfers=3,
        frames=3 + 1,
        payload_bytes=15,
        drops=1,
        inferiors=[
            SessionStatistics(
                transfers=3,
                frames=3,
                payload_bytes=15,
            ),
            SessionStatistics(
                transfers=1,
                frames=1,
                payload_bytes=6,
            ),
        ],
    )
    assert len(feedback) == 2
    feedback.sort(key=lambda x: x.inferior_session is not inf_a
                  )  # Ensure consistent ordering
    assert feedback[0].inferior_session is inf_a
    assert feedback[0].original_transfer_timestamp == ts
    assert ts.system <= feedback[
        0].first_frame_transmission_timestamp.system <= time.time()
    assert ts.monotonic <= feedback[
        0].first_frame_transmission_timestamp.monotonic <= time.monotonic()
    assert isinstance(feedback[0].inferior_feedback, LoopbackFeedback)
    feedback.pop(0)
    assert len(feedback) == 1
    assert feedback[0].inferior_session is inf_b
    assert feedback[0].original_transfer_timestamp == ts
    assert ts.system <= feedback[
        0].first_frame_transmission_timestamp.system <= time.time()
    assert ts.monotonic <= feedback[
        0].first_frame_transmission_timestamp.monotonic <= time.monotonic()
    assert isinstance(feedback[0].inferior_feedback, LoopbackFeedback)
    feedback.pop()
    assert not feedback
    tf_rx = await (rx_a.receive(loop.time() + 1))
    assert isinstance(tf_rx, TransferFrom)
    assert tf_rx.transfer_id == 777777777777
    assert tf_rx.fragmented_payload == [memoryview(b"fgsfds")]
    tf_rx = await (rx_b.receive(loop.time() + 1))
    assert isinstance(tf_rx, TransferFrom)
    assert tf_rx.transfer_id == 777777777777
    assert tf_rx.fragmented_payload == [memoryview(b"fgsfds")]

    # Remove the first inferior.
    ses._close_inferior(0)  # pylint: disable=protected-access
    assert ses.inferiors == [inf_b]
    ses._close_inferior(1)  # Out of range, no effect.  # pylint: disable=protected-access
    assert ses.inferiors == [inf_b]
    # Make sure the removed inferior has been closed.
    assert not tr_a.output_sessions

    # Transmission test with the last inferior.
    assert await (ses.send(
        Transfer(
            timestamp=ts,
            priority=Priority.HIGH,
            transfer_id=88888888888888,
            fragmented_payload=[memoryview(b"hedgehog")],
        ),
        loop.time() + 1.0,
    ))
    assert ses.sample_statistics().transfers == 4
    # We don't check frames because this stat metric is computed quite clumsily atm, this may change later.
    assert ses.sample_statistics().payload_bytes == 23
    assert ses.sample_statistics().drops == 1
    assert ses.sample_statistics().inferiors == [
        SessionStatistics(
            transfers=2,
            frames=2,
            payload_bytes=14,
        ),
    ]
    assert len(feedback) == 1
    assert feedback[0].inferior_session is inf_b
    assert feedback[0].original_transfer_timestamp == ts
    assert ts.system <= feedback[
        0].first_frame_transmission_timestamp.system <= time.time()
    assert ts.monotonic <= feedback[
        0].first_frame_transmission_timestamp.monotonic <= time.monotonic()
    assert isinstance(feedback[0].inferior_feedback, LoopbackFeedback)
    feedback.pop()
    assert not feedback
    assert None is await (rx_a.receive(loop.time() + 1))
    tf_rx = await (rx_b.receive(loop.time() + 1))
    assert isinstance(tf_rx, TransferFrom)
    assert tf_rx.transfer_id == 88888888888888
    assert tf_rx.fragmented_payload == [memoryview(b"hedgehog")]

    # Disable the feedback.
    ses.disable_feedback()
    # A diversion - enable the feedback in the inferior and make sure it's not propagated.
    ses._enable_feedback_on_inferior(inf_b)  # pylint: disable=protected-access
    assert await (ses.send(
        Transfer(
            timestamp=ts,
            priority=Priority.OPTIONAL,
            transfer_id=666666666666666,
            fragmented_payload=[memoryview(b"horse")],
        ),
        loop.time() + 1.0,
    ))
    assert ses.sample_statistics().transfers == 5
    # We don't check frames because this stat metric is computed quite clumsily atm, this may change later.
    assert ses.sample_statistics().payload_bytes == 28
    assert ses.sample_statistics().drops == 1
    assert ses.sample_statistics().inferiors == [
        SessionStatistics(
            transfers=3,
            frames=3,
            payload_bytes=19,
        ),
    ]
    assert not feedback
    assert None is await (rx_a.receive(loop.time() + 1))
    tf_rx = await (rx_b.receive(loop.time() + 1))
    assert isinstance(tf_rx, TransferFrom)
    assert tf_rx.transfer_id == 666666666666666
    assert tf_rx.fragmented_payload == [memoryview(b"horse")]

    # Retirement.
    assert not is_retired
    ses.close()
    assert is_retired
    # Make sure the inferiors have been closed.
    assert not tr_a.output_sessions
    assert not tr_b.output_sessions
    # Idempotency.
    is_retired = False
    ses.close()
    assert not is_retired

    # Use after close.
    with pytest.raises(ResourceClosedError):
        await (ses.send(
            Transfer(
                timestamp=ts,
                priority=Priority.OPTIONAL,
                transfer_id=1111111111111,
                fragmented_payload=[memoryview(b"cat")],
            ),
            loop.time() + 1.0,
        ))

    assert None is await (rx_a.receive(loop.time() + 1))
    assert None is await (rx_b.receive(loop.time() + 1))

    await asyncio.sleep(2.0)
コード例 #14
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()
コード例 #15
0
ファイル: _serial.py プロジェクト: thirtytwobits/pyuavcan
async def _unittest_serial_transport_capture(caplog: typing.Any) -> None:
    from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer
    from pyuavcan.transport import Priority, Timestamp, OutputSessionSpecifier

    get_monotonic = asyncio.get_event_loop().time

    tr = SerialTransport(serial_port="loop://",
                         local_node_id=42,
                         mtu=1024,
                         service_transfer_multiplier=2)
    sft_capacity = 1024
    payload_single = [_mem("qwertyui"), _mem("01234567")
                      ] * (sft_capacity // 16)
    assert sum(map(len, payload_single)) == sft_capacity
    payload_x3 = (payload_single * 3)[:-1]
    payload_x3_size_bytes = sft_capacity * 3 - 8
    assert sum(map(len, payload_x3)) == payload_x3_size_bytes

    broadcaster = tr.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None),
        PayloadMetadata(10000))
    client_requester = tr.get_output_session(
        OutputSessionSpecifier(
            ServiceDataSpecifier(333, ServiceDataSpecifier.Role.REQUEST),
            3210),
        PayloadMetadata(10000),
    )

    events: typing.List[SerialCapture] = []
    events2: typing.List[pyuavcan.transport.Capture] = []

    def append_events(cap: pyuavcan.transport.Capture) -> None:
        assert isinstance(cap, SerialCapture)
        events.append(cap)

    tr.begin_capture(append_events)
    tr.begin_capture(events2.append)
    assert events == []
    assert events2 == []

    #
    # Multi-frame message.
    #
    ts = Timestamp.now()
    assert await broadcaster.send(
        Transfer(timestamp=ts,
                 priority=Priority.LOW,
                 transfer_id=777,
                 fragmented_payload=payload_x3),
        monotonic_deadline=get_monotonic() + 5.0,
    )
    await asyncio.sleep(0.1)
    assert events == events2
    # Send three, receive three.
    # Sorting is required because the ordering of the events in the middle is not defined: arrival events
    # may or may not be registered before the emission event depending on how the serial loopback is operating.
    a, b, c, d, e, f = sorted(events, key=lambda x: not x.own)
    assert isinstance(a, SerialCapture) and a.own
    assert isinstance(b, SerialCapture) and b.own
    assert isinstance(c, SerialCapture) and c.own
    assert isinstance(d, SerialCapture) and not d.own
    assert isinstance(e, SerialCapture) and not e.own
    assert isinstance(f, SerialCapture) and not f.own

    def parse(x: SerialCapture) -> SerialFrame:
        out = SerialFrame.parse_from_cobs_image(x.fragment)
        assert out is not None
        return out

    assert parse(a).transfer_id == 777
    assert parse(b).transfer_id == 777
    assert parse(c).transfer_id == 777
    assert a.timestamp.monotonic >= ts.monotonic
    assert b.timestamp.monotonic >= ts.monotonic
    assert c.timestamp.monotonic >= ts.monotonic
    assert parse(a).index == 0
    assert parse(b).index == 1
    assert parse(c).index == 2
    assert not parse(a).end_of_transfer
    assert not parse(b).end_of_transfer
    assert parse(c).end_of_transfer

    assert a.fragment.tobytes().strip(b"\x00") == d.fragment.tobytes().strip(
        b"\x00")
    assert b.fragment.tobytes().strip(b"\x00") == e.fragment.tobytes().strip(
        b"\x00")
    assert c.fragment.tobytes().strip(b"\x00") == f.fragment.tobytes().strip(
        b"\x00")

    events.clear()
    events2.clear()

    #
    # Single-frame service request with dual frame duplication.
    #
    ts = Timestamp.now()
    assert await client_requester.send(
        Transfer(timestamp=ts,
                 priority=Priority.HIGH,
                 transfer_id=888,
                 fragmented_payload=payload_single),
        monotonic_deadline=get_monotonic() + 5.0,
    )
    await asyncio.sleep(0.1)
    assert events == events2
    # Send two, receive two.
    # Sorting is required because the order of the two events in the middle is not defined: the arrival event
    # may or may not be registered before the emission event depending on how the serial loopback is operating.
    a, b, c, d = sorted(events, key=lambda x: not x.own)
    assert isinstance(a, SerialCapture) and a.own
    assert isinstance(b, SerialCapture) and b.own
    assert isinstance(c, SerialCapture) and not c.own
    assert isinstance(d, SerialCapture) and not d.own

    assert parse(a).transfer_id == 888
    assert parse(b).transfer_id == 888
    assert a.timestamp.monotonic >= ts.monotonic
    assert b.timestamp.monotonic >= ts.monotonic
    assert parse(a).index == 0
    assert parse(b).index == 0
    assert parse(a).end_of_transfer
    assert parse(b).end_of_transfer

    assert a.fragment.tobytes().strip(b"\x00") == c.fragment.tobytes().strip(
        b"\x00")
    assert b.fragment.tobytes().strip(b"\x00") == d.fragment.tobytes().strip(
        b"\x00")

    events.clear()
    events2.clear()

    #
    # Out-of-band data.
    #
    grownups = b"Aren't there any grownups at all? - No grownups!\x00"
    with caplog.at_level(logging.CRITICAL,
                         logger=pyuavcan.transport.serial.__name__):
        # The frame delimiter is needed to force new frame into the state machine.
        tr.serial_port.write(grownups)
        await asyncio.sleep(1)
    assert events == events2
    (oob, ) = events
    assert isinstance(oob, SerialCapture)
    assert not oob.own
    assert bytes(oob.fragment) == grownups

    events.clear()
    events2.clear()
コード例 #16
0
ファイル: _redundant.py プロジェクト: yiyang0219/pyuavcan
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.
コード例 #17
0
async def _unittest_output_session() -> None:
    ts = Timestamp.now()
    loop = asyncio.get_event_loop()
    loop.slow_callback_duration = 5.0  # TODO use asyncio socket read and remove this thing.
    finalized = False

    def do_finalize() -> None:
        nonlocal finalized
        finalized = True

    def check_timestamp(t: Timestamp) -> bool:
        now = Timestamp.now()
        s = ts.system_ns <= t.system_ns <= now.system_ns
        m = ts.monotonic_ns <= t.monotonic_ns <= now.system_ns
        return s and m

    destination_endpoint = "127.100.0.1", 25406

    sock_rx = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM)
    sock_rx.bind(destination_endpoint)
    sock_rx.settimeout(1.0)

    def make_sock() -> socket_.socket:
        sock = socket_.socket(socket_.AF_INET, socket_.SOCK_DGRAM)
        sock.bind(("127.100.0.2", 0))
        sock.connect(destination_endpoint)
        sock.setblocking(False)
        return sock

    sos = UDPOutputSession(
        specifier=OutputSessionSpecifier(MessageDataSpecifier(3210), None),
        payload_metadata=PayloadMetadata(1024),
        mtu=11,
        multiplier=1,
        sock=make_sock(),
        finalizer=do_finalize,
    )

    assert sos.specifier == OutputSessionSpecifier(MessageDataSpecifier(3210),
                                                   None)
    assert sos.destination_node_id is None
    assert sos.payload_metadata == PayloadMetadata(1024)
    assert sos.sample_statistics() == SessionStatistics()

    assert await (sos.send(
        Transfer(
            timestamp=ts,
            priority=Priority.NOMINAL,
            transfer_id=12340,
            fragmented_payload=[
                memoryview(b"one"),
                memoryview(b"two"),
                memoryview(b"three")
            ],
        ),
        loop.time() + 10.0,
    ))

    rx_data, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == "127.100.0.2"
    assert rx_data == (
        b"\x00\x04\x00\x00\x00\x00\x00\x8040\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        + b"one" + b"two" + b"three")
    with raises(socket_.timeout):
        sock_rx.recvfrom(1000)

    last_feedback: typing.Optional[Feedback] = None

    def feedback_handler(feedback: Feedback) -> None:
        nonlocal last_feedback
        last_feedback = feedback

    sos.enable_feedback(feedback_handler)

    assert last_feedback is None
    assert await (sos.send(
        Transfer(timestamp=ts,
                 priority=Priority.NOMINAL,
                 transfer_id=12340,
                 fragmented_payload=[]),
        loop.time() + 10.0,
    ))
    assert last_feedback is not None
    assert last_feedback.original_transfer_timestamp == ts
    assert check_timestamp(last_feedback.first_frame_transmission_timestamp)

    sos.disable_feedback()
    sos.disable_feedback()  # Idempotency check

    _, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == "127.100.0.2"
    with raises(socket_.timeout):
        sock_rx.recvfrom(1000)

    assert sos.sample_statistics() == SessionStatistics(transfers=2,
                                                        frames=2,
                                                        payload_bytes=11,
                                                        errors=0,
                                                        drops=0)

    assert sos.socket.fileno() >= 0
    assert not finalized
    sos.close()
    assert finalized
    assert sos.socket.fileno() < 0  # The socket is supposed to be disposed of.
    finalized = False

    # Multi-frame with multiplication
    sos = UDPOutputSession(
        specifier=OutputSessionSpecifier(
            ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST),
            2222),
        payload_metadata=PayloadMetadata(1024),
        mtu=10,
        multiplier=2,
        sock=make_sock(),
        finalizer=do_finalize,
    )
    assert await (sos.send(
        Transfer(
            timestamp=ts,
            priority=Priority.OPTIONAL,
            transfer_id=54321,
            fragmented_payload=[
                memoryview(b"one"),
                memoryview(b"two"),
                memoryview(b"three")
            ],
        ),
        loop.time() + 10.0,
    ))
    data_main_a, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == "127.100.0.2"
    data_main_b, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == "127.100.0.2"
    data_redundant_a, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == "127.100.0.2"
    data_redundant_b, endpoint = sock_rx.recvfrom(1000)
    assert endpoint[0] == "127.100.0.2"
    with raises(socket_.timeout):
        sock_rx.recvfrom(1000)

    print("data_main_a", data_main_a)
    print("data_main_b", data_main_b)
    print("data_redundant_a", data_redundant_a)
    print("data_redundant_b", data_redundant_b)

    assert data_main_a == data_redundant_a
    assert data_main_b == data_redundant_b
    assert data_main_a == (
        b"\x00\x07\x00\x00\x00\x00\x00\x001\xd4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        + b"one" + b"two" + b"three"[:-1])
    assert data_main_b == (
        b"\x00\x07\x00\x00\x01\x00\x00\x801\xd4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        + b"e" + pyuavcan.transport.commons.crc.CRC32C.new(
            b"one", b"two", b"three").value_as_bytes)

    sos.socket.close()  # This is to prevent resource warning
    sos = UDPOutputSession(
        specifier=OutputSessionSpecifier(
            ServiceDataSpecifier(321, ServiceDataSpecifier.Role.REQUEST),
            2222),
        payload_metadata=PayloadMetadata(1024),
        mtu=10,
        multiplier=1,
        sock=make_sock(),
        finalizer=do_finalize,
    )

    # Induced timeout
    assert not await (sos.send(
        Transfer(
            timestamp=ts,
            priority=Priority.NOMINAL,
            transfer_id=12340,
            fragmented_payload=[
                memoryview(b"one"),
                memoryview(b"two"),
                memoryview(b"three")
            ],
        ),
        loop.time() - 0.1,  # Expired on arrival
    ))

    assert sos.sample_statistics() == SessionStatistics(
        transfers=0,
        frames=0,
        payload_bytes=0,
        errors=0,
        drops=2  # Because multiframe
    )

    # Induced failure
    sos.socket.close()
    with raises(OSError):
        assert not await (sos.send(
            Transfer(
                timestamp=ts,
                priority=Priority.NOMINAL,
                transfer_id=12340,
                fragmented_payload=[
                    memoryview(b"one"),
                    memoryview(b"two"),
                    memoryview(b"three")
                ],
            ),
            loop.time() + 10.0,
        ))

    assert sos.sample_statistics() == SessionStatistics(transfers=0,
                                                        frames=0,
                                                        payload_bytes=0,
                                                        errors=1,
                                                        drops=2)

    assert not finalized
    sos.close()
    assert finalized
    sos.close()  # Idempotency

    with raises(pyuavcan.transport.ResourceClosedError):
        await (sos.send(
            Transfer(
                timestamp=ts,
                priority=Priority.NOMINAL,
                transfer_id=12340,
                fragmented_payload=[
                    memoryview(b"one"),
                    memoryview(b"two"),
                    memoryview(b"three")
                ],
            ),
            loop.time() + 10.0,
        ))

    sock_rx.close()
コード例 #18
0
def _unittest_redundant_input_monotonic() -> None:
    import pytest
    from pyuavcan.transport import Transfer, Timestamp, Priority
    from pyuavcan.transport.loopback import LoopbackTransport

    loop = asyncio.get_event_loop()
    await_ = loop.run_until_complete

    spec = pyuavcan.transport.InputSessionSpecifier(
        pyuavcan.transport.MessageDataSpecifier(4321), None)
    spec_tx = pyuavcan.transport.OutputSessionSpecifier(
        spec.data_specifier, None)
    meta = pyuavcan.transport.PayloadMetadata(30)

    ts = Timestamp.now()

    tr_a = LoopbackTransport(111)
    tr_b = LoopbackTransport(111)
    tx_a = tr_a.get_output_session(spec_tx, meta)
    tx_b = tr_b.get_output_session(spec_tx, meta)
    inf_a = tr_a.get_input_session(spec, meta)
    inf_b = tr_b.get_input_session(spec, meta)

    inf_a.transfer_id_timeout = 1.1  # This is used to ensure that the transfer-ID timeout is handled correctly.

    ses = RedundantInputSession(
        spec,
        meta,
        tid_modulo_provider=lambda:
        None,  # Like UDP or serial - infinite modulo.
        loop=loop,
        finalizer=lambda: None,
    )
    assert ses.specifier is spec
    assert ses.payload_metadata is meta
    assert not ses.inferiors
    assert ses.sample_statistics() == RedundantSessionStatistics()
    assert pytest.approx(0.0) == ses.transfer_id_timeout

    # Add inferiors.
    ses._add_inferior(inf_a)  # No change, added above    # pylint: disable=protected-access
    assert ses.inferiors == [inf_a]
    ses._add_inferior(inf_b)  # pylint: disable=protected-access
    assert ses.inferiors == [inf_a, inf_b]

    ses.transfer_id_timeout = 1.1
    assert ses.transfer_id_timeout == pytest.approx(1.1)
    assert inf_a.transfer_id_timeout == pytest.approx(1.1)
    assert inf_b.transfer_id_timeout == pytest.approx(1.1)

    # Redundant reception from multiple interfaces concurrently.
    for tx_x in (tx_a, tx_b):
        assert await_(
            tx_x.send(
                Transfer(
                    timestamp=Timestamp.now(),
                    priority=Priority.HIGH,
                    transfer_id=2,
                    fragmented_payload=[memoryview(b"def")],
                ),
                loop.time() + 1.0,
            ))
        assert await_(
            tx_x.send(
                Transfer(
                    timestamp=Timestamp.now(),
                    priority=Priority.HIGH,
                    transfer_id=3,
                    fragmented_payload=[memoryview(b"ghi")],
                ),
                loop.time() + 1.0,
            ))

    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 2
    assert tr.fragmented_payload == [memoryview(b"def")]

    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 3
    assert tr.fragmented_payload == [memoryview(b"ghi")]

    assert None is await_(
        ses.receive(loop.time() + 2.0))  # Nothing left to read now.

    # This one will be accepted despite a smaller transfer-ID because of the TID timeout.
    assert await_(
        tx_a.send(
            Transfer(
                timestamp=Timestamp.now(),
                priority=Priority.HIGH,
                transfer_id=1,
                fragmented_payload=[memoryview(b"acc")],
            ),
            loop.time() + 1.0,
        ))
    tr = await_(ses.receive(loop.time() + 0.1))
    assert isinstance(tr, RedundantTransferFrom)
    assert ts.monotonic <= tr.timestamp.monotonic <= (loop.time() + 1e-3)
    assert tr.priority == Priority.HIGH
    assert tr.transfer_id == 1
    assert tr.fragmented_payload == [memoryview(b"acc")]
    assert tr.inferior_session == inf_a

    # Stats check.
    assert ses.sample_statistics() == RedundantSessionStatistics(
        transfers=3,
        frames=inf_a.sample_statistics().frames +
        inf_b.sample_statistics().frames,
        payload_bytes=9,
        errors=0,
        drops=0,
        inferiors=[
            inf_a.sample_statistics(),
            inf_b.sample_statistics(),
        ],
    )

    ses.close()
コード例 #19
0
def _unittest_redundant_output_exceptions() -> None:
    import pytest
    from pyuavcan.transport import Transfer, Timestamp, Priority, SessionStatistics
    from pyuavcan.transport import TransferFrom
    from pyuavcan.transport.loopback import LoopbackTransport

    loop = asyncio.get_event_loop()
    await_ = loop.run_until_complete

    spec = pyuavcan.transport.OutputSessionSpecifier(pyuavcan.transport.MessageDataSpecifier(4321), None)
    spec_rx = pyuavcan.transport.InputSessionSpecifier(spec.data_specifier, None)
    meta = pyuavcan.transport.PayloadMetadata(0x_deadbeef_deadbeef, 30 * 1024 * 1024)

    ts = Timestamp.now()

    is_retired = False

    def retire() -> None:
        nonlocal is_retired
        is_retired = True

    ses = RedundantOutputSession(spec, meta, loop=loop, finalizer=retire)
    assert not is_retired
    assert ses.specifier is spec
    assert ses.payload_metadata is meta
    assert not ses.inferiors
    assert ses.sample_statistics() == RedundantSessionStatistics()

    tr_a = LoopbackTransport(111)
    tr_b = LoopbackTransport(111)
    inf_a = tr_a.get_output_session(spec, meta)
    inf_b = tr_b.get_output_session(spec, meta)
    rx_a = tr_a.get_input_session(spec_rx, meta)
    rx_b = tr_b.get_input_session(spec_rx, meta)

    # noinspection PyProtectedMember
    ses._add_inferior(inf_a)
    # noinspection PyProtectedMember
    ses._add_inferior(inf_b)

    # Transmission with exceptions.
    # If at least one transmission succeeds, the call succeeds.
    inf_a.exception = RuntimeError('EXCEPTION SUKA')
    assert await_(ses.send_until(
        Transfer(timestamp=ts,
                 priority=Priority.FAST,
                 transfer_id=444444444444,
                 fragmented_payload=[memoryview(b'exception suka')]),
        loop.time() + 1.0
    ))
    assert ses.sample_statistics() == RedundantSessionStatistics(
        transfers=1,
        frames=1,
        payload_bytes=len('exception suka'),
        errors=0,
        drops=0,
        inferiors=[
            SessionStatistics(
                transfers=0,
                frames=0,
                payload_bytes=0,
            ),
            SessionStatistics(
                transfers=1,
                frames=1,
                payload_bytes=len('exception suka'),
            ),
        ],
    )
    assert None is await_(rx_a.receive_until(loop.time() + 1))
    tf_rx = await_(rx_b.receive_until(loop.time() + 1))
    assert isinstance(tf_rx, TransferFrom)
    assert tf_rx.transfer_id == 444444444444
    assert tf_rx.fragmented_payload == [memoryview(b'exception suka')]

    # Transmission timeout.
    # One times out, one raises an exception --> the result is timeout.
    inf_b.should_timeout = True
    assert not await_(ses.send_until(
        Transfer(timestamp=ts,
                 priority=Priority.FAST,
                 transfer_id=2222222222222,
                 fragmented_payload=[memoryview(b'exception suka')]),
        loop.time() + 1.0
    ))
    assert ses.sample_statistics().transfers == 1
    assert ses.sample_statistics().payload_bytes == len('exception suka')
    assert ses.sample_statistics().errors == 0
    assert ses.sample_statistics().drops == 1
    assert None is await_(rx_a.receive_until(loop.time() + 1))
    assert None is await_(rx_b.receive_until(loop.time() + 1))

    # Transmission with exceptions.
    # If all transmissions fail, the call fails.
    inf_b.exception = RuntimeError('EXCEPTION SUKA')
    with pytest.raises(RuntimeError, match='EXCEPTION SUKA'):
        assert await_(ses.send_until(
            Transfer(timestamp=ts,
                     priority=Priority.FAST,
                     transfer_id=3333333333333,
                     fragmented_payload=[memoryview(b'exception suka')]),
            loop.time() + 1.0
        ))
    assert ses.sample_statistics().transfers == 1
    assert ses.sample_statistics().payload_bytes == len('exception suka')
    assert ses.sample_statistics().errors == 1
    assert ses.sample_statistics().drops == 1
    assert None is await_(rx_a.receive_until(loop.time() + 1))
    assert None is await_(rx_b.receive_until(loop.time() + 1))

    # Retirement.
    assert not is_retired
    ses.close()
    assert is_retired
    # Make sure the inferiors have been closed.
    assert not tr_a.output_sessions
    assert not tr_b.output_sessions
    # Idempotency.
    is_retired = False
    ses.close()
    assert not is_retired