Ejemplo n.º 1
0
def get_transport(
        node_id: typing.Optional[int]) -> pyuavcan.transport.Transport:
    from pyuavcan.transport.udp import UDPTransport

    return UDPTransport(
        f"127.0.0.{node_id}") if node_id is not None else UDPTransport(
            "127.0.0.1", anonymous=True)
Ejemplo n.º 2
0
 def one(nid: typing.Optional[int]) -> RedundantTransport:
     red = RedundantTransport()
     red.attach_inferior(
         UDPTransport(f'127.0.0.{nid}/8')
         if nid is not None else UDPTransport('127.255.255.255/8'))
     red.attach_inferior(SerialTransport(VIRTUAL_BUS_URI, nid))
     print('REDUNDANT TRANSPORT UDP+SERIAL:', red)
     return red
Ejemplo n.º 3
0
 def make_udp_serial(nid: typing.Optional[int]) -> pyuavcan.transport.Transport:
     tr = RedundantTransport()
     if nid is not None:
         tr.attach_inferior(UDPTransport(f"127.0.0.{nid}"))
     else:
         tr.attach_inferior(UDPTransport(f"127.0.0.1", anonymous=True))
     tr.attach_inferior(SerialTransport("socket://localhost:50905", local_node_id=nid))
     return tr
Ejemplo n.º 4
0
 def one(nid: typing.Optional[int]) -> RedundantTransport:
     red = RedundantTransport()
     if nid is not None:
         red.attach_inferior(UDPTransport(f"127.0.0.{nid}"))
     else:
         red.attach_inferior(UDPTransport("127.0.0.1", anonymous=True))
     red.attach_inferior(SerialTransport(VIRTUAL_BUS_URI, nid))
     print("UDP+SERIAL:", red)
     return red
Ejemplo n.º 5
0
def _unittest_udp_tracer() -> None:
    from pytest import approx
    from ipaddress import ip_address
    from pyuavcan.transport import Priority, ServiceDataSpecifier
    from pyuavcan.transport.udp import UDPTransport
    from ._ip import MACHeader, IPHeader, UDPHeader, service_data_specifier_to_udp_port

    tr = UDPTransport.make_tracer()
    ts = Timestamp.now()

    ds = ServiceDataSpecifier(11, ServiceDataSpecifier.Role.RESPONSE)
    trace = tr.update(
        UDPCapture(
            ts,
            RawPacket(
                MACHeader(memoryview(b""), memoryview(b"")),
                IPHeader(ip_address("127.0.0.42"), ip_address("127.0.0.63")),
                UDPHeader(12345, service_data_specifier_to_udp_port(ds)),
                memoryview(b"".join(
                    UDPFrame(
                        priority=Priority.SLOW,
                        transfer_id=1234567890,
                        index=0,
                        end_of_transfer=True,
                        payload=memoryview(b"Hello world!"),
                    ).compile_header_and_payload())),
            ),
        ))
    assert isinstance(trace, TransferTrace)
    assert trace.timestamp == ts
    assert trace.transfer_id_timeout == approx(
        AlienTransferReassembler.MAX_TRANSFER_ID_TIMEOUT)  # Initial value.
    assert trace.transfer.metadata.transfer_id == 1234567890
    assert trace.transfer.metadata.priority == Priority.SLOW
    assert trace.transfer.metadata.session_specifier.source_node_id == 42
    assert trace.transfer.metadata.session_specifier.destination_node_id == 63
    assert trace.transfer.metadata.session_specifier.data_specifier == ds
    assert trace.transfer.fragmented_payload == [memoryview(b"Hello world!")]

    assert None is tr.update(
        pyuavcan.transport.Capture(ts))  # Another transport, ignored.

    assert None is tr.update(
        UDPCapture(  # Malformed frame.
            ts,
            RawPacket(
                MACHeader(memoryview(b""), memoryview(b"")),
                IPHeader(ip_address("127.0.0.42"), ip_address("127.1.0.63")),
                UDPHeader(1, 1),
                memoryview(b""),
            ),
        ))
Ejemplo n.º 6
0
def _make_udp(
        registers: MutableMapping[str, ValueProxy],
        node_id: Optional[int]) -> Iterator[pyuavcan.transport.Transport]:
    def init(name: str, default: RelaxedValue) -> ValueProxy:
        return registers.setdefault("uavcan.udp." + name, ValueProxy(default))

    ip_list = str(init("iface", "")).split()
    mtu = int(init("mtu", Natural16([1200])))
    srv_mult = int(init("duplicate_service_transfers", False)) + 1

    if ip_list:
        from pyuavcan.transport.udp import UDPTransport

        for ip in ip_list:
            yield UDPTransport(ip,
                               node_id,
                               mtu=mtu,
                               service_transfer_multiplier=srv_mult)
Ejemplo n.º 7
0
 def one(nid: typing.Optional[int]) -> UDPTransport:
     return UDPTransport(
         f"127.0.0.{nid}") if nid is not None else UDPTransport(
             "127.0.0.1", anonymous=True)
Ejemplo n.º 8
0
async def _unittest_udp_transport() -> None:
    from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom
    from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier
    from pyuavcan.transport import ProtocolParameters

    get_monotonic = asyncio.get_event_loop().time

    with pytest.raises(ValueError):
        _ = UDPTransport(ip_address='127.0.0.111/8', mtu=10)

    with pytest.raises(ValueError):
        _ = UDPTransport(ip_address='127.0.0.111/8',
                         service_transfer_multiplier=100)

    tr = UDPTransport('127.0.0.111/8', mtu=9000)
    tr2 = UDPTransport('127.0.0.222/8', service_transfer_multiplier=2)

    assert tr.local_ip_address_with_netmask == '127.0.0.111/8'
    assert tr2.local_ip_address_with_netmask == '127.0.0.222/8'

    assert tr.loop is asyncio.get_event_loop()
    assert tr.local_node_id == 111
    assert tr2.local_node_id == 222

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

    assert list(xml.etree.ElementTree.fromstring(
        tr.descriptor).itertext()) == ['127.0.0.111/8']
    assert tr.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2**56,
        max_nodes=2**UDPTransport.NODE_ID_BIT_LENGTH,
        mtu=9000,
    )

    assert list(xml.etree.ElementTree.fromstring(
        tr2.descriptor).itertext()) == ['127.0.0.222/8']
    assert tr2.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2**56,
        max_nodes=2**UDPTransport.NODE_ID_BIT_LENGTH,
        mtu=UDPTransport.DEFAULT_MTU,
    )

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

    payload_single = [_mem('qwertyui'), _mem('01234567')
                      ] * (UDPTransport.DEFAULT_MTU // 16)
    assert sum(map(len, payload_single)) == UDPTransport.DEFAULT_MTU

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

    #
    # Instantiate session objects.
    #
    meta = PayloadMetadata(0x_bad_c0ffee_0dd_f00d, 10000)

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

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

    subscriber_selective = tr.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(12345), 123), meta)
    assert subscriber_selective is tr.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(12345), 123), meta)

    server_listener = tr.get_input_session(
        InputSessionSpecifier(
            ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST),
            None), meta)
    assert server_listener is tr.get_input_session(
        InputSessionSpecifier(
            ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST),
            None), meta)

    server_responder = tr.get_output_session(
        OutputSessionSpecifier(
            ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE),
            222), meta)
    assert server_responder is tr.get_output_session(
        OutputSessionSpecifier(
            ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE),
            222), meta)

    client_requester = tr2.get_output_session(
        OutputSessionSpecifier(
            ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111),
        meta)
    assert client_requester is tr2.get_output_session(
        OutputSessionSpecifier(
            ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111),
        meta)

    client_listener = tr2.get_input_session(
        InputSessionSpecifier(
            ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE),
            111), meta)
    assert client_listener is tr2.get_input_session(
        InputSessionSpecifier(
            ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE),
            111), meta)

    print('tr :', tr.input_sessions, tr.output_sessions)
    assert set(tr.input_sessions) == {
        subscriber_promiscuous, subscriber_selective, server_listener
    }
    assert set(tr.output_sessions) == {server_responder}

    print('tr2:', tr2.input_sessions, tr2.output_sessions)
    assert set(tr2.input_sessions) == {client_listener}
    assert set(tr2.output_sessions) == {broadcaster, client_requester}

    assert tr.sample_statistics().demultiplexer[MessageDataSpecifier(
        12345)].accepted_datagrams == {}
    assert tr.sample_statistics().demultiplexer[ServiceDataSpecifier(
        444, ServiceDataSpecifier.Role.REQUEST)].accepted_datagrams == {}

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

    #
    # Message exchange test.
    #
    assert await broadcaster.send_until(
        Transfer(timestamp=Timestamp.now(),
                 priority=Priority.LOW,
                 transfer_id=77777,
                 fragmented_payload=payload_single),
        monotonic_deadline=get_monotonic() + 5.0)

    rx_transfer = await subscriber_promiscuous.receive_until(get_monotonic() +
                                                             5.0)
    print('PROMISCUOUS SUBSCRIBER TRANSFER:', rx_transfer)
    assert isinstance(rx_transfer, TransferFrom)
    assert rx_transfer.priority == Priority.LOW
    assert rx_transfer.transfer_id == 77777
    assert rx_transfer.fragmented_payload == [b''.join(payload_single)
                                              ]  # type: ignore

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

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

    #
    # Service exchange test.
    #
    assert await client_requester.send_until(
        Transfer(timestamp=Timestamp.now(),
                 priority=Priority.HIGH,
                 transfer_id=88888,
                 fragmented_payload=payload_x3),
        monotonic_deadline=get_monotonic() + 5.0)

    rx_transfer = await server_listener.receive_until(get_monotonic() + 5.0)
    print('SERVER LISTENER TRANSFER:', rx_transfer)
    assert isinstance(rx_transfer, TransferFrom)
    assert rx_transfer.priority == Priority.HIGH
    assert rx_transfer.transfer_id == 88888
    assert len(rx_transfer.fragmented_payload) == 3
    assert b''.join(rx_transfer.fragmented_payload) == b''.join(payload_x3)

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

    print('tr :', tr.sample_statistics())
    assert tr.sample_statistics().demultiplexer[MessageDataSpecifier(
        12345)].accepted_datagrams == {
            222: 1
        }
    assert tr.sample_statistics().demultiplexer[ServiceDataSpecifier(
        444, ServiceDataSpecifier.Role.REQUEST
    )].accepted_datagrams == {
        222: 3 * 2
    }  # Deterministic data loss mitigation is enabled, multiplication factor 2
    print('tr2:', tr2.sample_statistics())
    assert tr2.sample_statistics().demultiplexer[ServiceDataSpecifier(
        444, ServiceDataSpecifier.Role.RESPONSE)].accepted_datagrams == {}

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

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

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

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

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

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

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

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

    with pytest.raises(pyuavcan.transport.ResourceClosedError):
        _ = tr2.get_input_session(
            InputSessionSpecifier(MessageDataSpecifier(12345), None), meta)
Ejemplo n.º 9
0
async def _unittest_redundant_transport(caplog: typing.Any) -> None:
    from pyuavcan.transport import MessageDataSpecifier, PayloadMetadata, Transfer
    from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier
    from pyuavcan.transport import ProtocolParameters

    loop = asyncio.get_event_loop()
    loop.slow_callback_duration = 1.0

    tr_a = RedundantTransport()
    tr_b = RedundantTransport(loop)
    assert tr_a.sample_statistics() == RedundantTransportStatistics([])
    assert tr_a.inferiors == []
    assert tr_a.local_node_id is None
    assert tr_a.loop is asyncio.get_event_loop()
    assert tr_a.local_node_id is None
    assert tr_a.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=0,
        max_nodes=0,
        mtu=0,
    )
    assert tr_a.descriptor == '<redundant></redundant>'  # Empty, no inferiors.
    assert tr_a.input_sessions == []
    assert tr_a.output_sessions == []

    assert tr_a.loop == tr_b.loop

    #
    # Instantiate session objects.
    #
    meta = PayloadMetadata(10_240)

    pub_a = tr_a.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    sub_any_a = tr_a.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    assert pub_a is tr_a.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    assert set(tr_a.input_sessions) == {sub_any_a}
    assert set(tr_a.output_sessions) == {pub_a}
    assert tr_a.sample_statistics() == RedundantTransportStatistics()

    pub_b = tr_b.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    sub_any_b = tr_b.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), None), meta)
    sub_sel_b = tr_b.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta)
    assert sub_sel_b is tr_b.get_input_session(
        InputSessionSpecifier(MessageDataSpecifier(2345), 3210), meta)
    assert set(tr_b.input_sessions) == {sub_any_b, sub_sel_b}
    assert set(tr_b.output_sessions) == {pub_b}
    assert tr_b.sample_statistics() == RedundantTransportStatistics()

    #
    # Exchange test with no inferiors, expected to fail.
    #
    assert len(pub_a.inferiors) == 0
    assert len(sub_any_a.inferiors) == 0
    assert not await pub_a.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=1,
        fragmented_payload=[memoryview(b'abc')]),
                                      monotonic_deadline=loop.time() + 1.0)
    assert not await sub_any_a.receive_until(loop.time() + 0.1)
    assert not await sub_any_b.receive_until(loop.time() + 0.1)
    assert tr_a.sample_statistics() == RedundantTransportStatistics()
    assert tr_b.sample_statistics() == RedundantTransportStatistics()

    #
    # Adding inferiors - loopback, transport A only.
    #
    with pytest.raises(InconsistentInferiorConfigurationError,
                       match='(?i).*loop.*'):
        tr_a.attach_inferior(
            LoopbackTransport(
                111, loop=asyncio.new_event_loop()))  # Wrong event loop.
    assert len(pub_a.inferiors) == 0
    assert len(sub_any_a.inferiors) == 0

    lo_mono_0 = LoopbackTransport(111)
    lo_mono_1 = LoopbackTransport(111)

    tr_a.attach_inferior(lo_mono_0)
    assert len(pub_a.inferiors) == 1
    assert len(sub_any_a.inferiors) == 1

    with pytest.raises(ValueError):
        tr_a.detach_inferior(lo_mono_1)  # Not a registered inferior (yet).

    tr_a.attach_inferior(lo_mono_1)
    assert len(pub_a.inferiors) == 2
    assert len(sub_any_a.inferiors) == 2

    with pytest.raises(ValueError):
        tr_a.attach_inferior(lo_mono_0)  # Double-add not allowed.

    with pytest.raises(InconsistentInferiorConfigurationError,
                       match='(?i).*node-id.*'):
        tr_a.attach_inferior(LoopbackTransport(None))  # Wrong node-ID.

    with pytest.raises(InconsistentInferiorConfigurationError,
                       match='(?i).*node-id.*'):
        tr_a.attach_inferior(LoopbackTransport(1230))  # Wrong node-ID.

    assert tr_a.inferiors == [lo_mono_0, lo_mono_1]
    assert len(pub_a.inferiors) == 2
    assert len(sub_any_a.inferiors) == 2

    assert tr_a.sample_statistics() == RedundantTransportStatistics(inferiors=[
        lo_mono_0.sample_statistics(),
        lo_mono_1.sample_statistics(),
    ])
    assert tr_a.local_node_id == 111
    assert tr_a.descriptor == '<redundant><loopback/><loopback/></redundant>'

    assert await pub_a.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=2,
        fragmented_payload=[memoryview(b'def')]),
                                  monotonic_deadline=loop.time() + 1.0)
    rx = await sub_any_a.receive_until(loop.time() + 1.0)
    assert rx is not None
    assert rx.fragmented_payload == [memoryview(b'def')]
    assert rx.transfer_id == 2
    assert not await sub_any_b.receive_until(loop.time() + 0.1)

    #
    # Incapacitate one inferior, ensure things are still OK.
    #
    with caplog.at_level(logging.CRITICAL,
                         logger=pyuavcan.transport.redundant.__name__):
        for s in lo_mono_0.output_sessions:
            s.exception = RuntimeError('INTENDED EXCEPTION')

        assert await pub_a.send_until(Transfer(
            timestamp=Timestamp.now(),
            priority=Priority.LOW,
            transfer_id=3,
            fragmented_payload=[memoryview(b'qwe')]),
                                      monotonic_deadline=loop.time() + 1.0)
        rx = await sub_any_a.receive_until(loop.time() + 1.0)
        assert rx is not None
        assert rx.fragmented_payload == [memoryview(b'qwe')]
        assert rx.transfer_id == 3

    #
    # Remove old loopback transports. Configure new ones with cyclic TID.
    #
    lo_cyc_0 = LoopbackTransport(111)
    lo_cyc_1 = LoopbackTransport(111)
    cyc_proto_params = ProtocolParameters(
        transfer_id_modulo=32,  # Like CAN
        max_nodes=128,  # Like CAN
        mtu=63,  # Like CAN
    )
    lo_cyc_0.protocol_parameters = cyc_proto_params
    lo_cyc_1.protocol_parameters = cyc_proto_params
    assert lo_cyc_0.protocol_parameters == lo_cyc_1.protocol_parameters == cyc_proto_params

    assert tr_a.protocol_parameters.transfer_id_modulo >= 2**56
    with pytest.raises(InconsistentInferiorConfigurationError,
                       match='(?i).*transfer-id.*'):
        tr_a.attach_inferior(lo_cyc_0)  # Transfer-ID modulo mismatch

    tr_a.detach_inferior(lo_mono_0)
    tr_a.detach_inferior(lo_mono_1)
    del lo_mono_0  # Prevent accidental reuse.
    del lo_mono_1
    assert tr_a.inferiors == []  # All removed, okay.
    assert pub_a.inferiors == []
    assert sub_any_a.inferiors == []
    assert tr_a.local_node_id is None  # Back to the roots
    assert tr_a.descriptor == '<redundant></redundant>'  # Yes yes

    # Now we can add our cyclic transports safely.
    tr_a.attach_inferior(lo_cyc_0)
    assert tr_a.protocol_parameters.transfer_id_modulo == 32
    tr_a.attach_inferior(lo_cyc_1)
    assert tr_a.protocol_parameters == cyc_proto_params, 'Protocol parameter mismatch'
    assert tr_a.local_node_id == 111
    assert tr_a.descriptor == '<redundant><loopback/><loopback/></redundant>'

    # Exchange test.
    assert await pub_a.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=4,
        fragmented_payload=[memoryview(b'rty')]),
                                  monotonic_deadline=loop.time() + 1.0)
    rx = await sub_any_a.receive_until(loop.time() + 1.0)
    assert rx is not None
    assert rx.fragmented_payload == [memoryview(b'rty')]
    assert rx.transfer_id == 4

    #
    # Real heterogeneous transport test.
    #
    tr_a.detach_inferior(lo_cyc_0)
    tr_a.detach_inferior(lo_cyc_1)
    del lo_cyc_0  # Prevent accidental reuse.
    del lo_cyc_1

    udp_a = UDPTransport('127.0.0.111/8')
    udp_b = UDPTransport('127.0.0.222/8')

    serial_a = SerialTransport(SERIAL_URI, 111)
    serial_b = SerialTransport(SERIAL_URI, 222, mtu=2048)  # Heterogeneous.

    tr_a.attach_inferior(udp_a)
    tr_a.attach_inferior(serial_a)

    tr_b.attach_inferior(udp_b)
    tr_b.attach_inferior(serial_b)

    print('tr_a.descriptor', tr_a.descriptor)
    print('tr_b.descriptor', tr_b.descriptor)

    assert tr_a.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2**64,
        max_nodes=4096,
        mtu=1024,
    )
    assert tr_a.local_node_id == 111
    assert tr_a.descriptor == f'<redundant>{udp_a.descriptor}{serial_a.descriptor}</redundant>'

    assert tr_b.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2**64,
        max_nodes=4096,
        mtu=1024,
    )
    assert tr_b.local_node_id == 222
    assert tr_b.descriptor == f'<redundant>{udp_b.descriptor}{serial_b.descriptor}</redundant>'

    assert await pub_a.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=5,
        fragmented_payload=[memoryview(b'uio')]),
                                  monotonic_deadline=loop.time() + 1.0)
    rx = await sub_any_b.receive_until(loop.time() + 1.0)
    assert rx is not None
    assert rx.fragmented_payload == [memoryview(b'uio')]
    assert rx.transfer_id == 5
    assert not await sub_any_a.receive_until(loop.time() + 0.1)
    assert not await sub_any_b.receive_until(loop.time() + 0.1)
    assert not await sub_sel_b.receive_until(loop.time() + 0.1)

    #
    # Construct new session with the transports configured.
    #
    pub_a_new = tr_a.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), 222), meta)
    assert pub_a_new is tr_a.get_output_session(
        OutputSessionSpecifier(MessageDataSpecifier(2345), 222), meta)
    assert set(tr_a.output_sessions) == {pub_a, pub_a_new}

    assert await pub_a_new.send_until(Transfer(
        timestamp=Timestamp.now(),
        priority=Priority.LOW,
        transfer_id=6,
        fragmented_payload=[memoryview(b'asd')]),
                                      monotonic_deadline=loop.time() + 1.0)
    rx = await sub_any_b.receive_until(loop.time() + 1.0)
    assert rx is not None
    assert rx.fragmented_payload == [memoryview(b'asd')]
    assert rx.transfer_id == 6

    #
    # Termination.
    #
    tr_a.close()
    tr_a.close()  # Idempotency
    tr_b.close()
    tr_b.close()  # Idempotency

    with pytest.raises(pyuavcan.transport.ResourceClosedError
                       ):  # Make sure the inferiors are closed.
        udp_a.get_output_session(
            OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    with pytest.raises(pyuavcan.transport.ResourceClosedError
                       ):  # Make sure the inferiors are closed.
        serial_b.get_output_session(
            OutputSessionSpecifier(MessageDataSpecifier(2345), None), meta)

    with pytest.raises(pyuavcan.transport.ResourceClosedError
                       ):  # Make sure the sessions are closed.
        await pub_a.send_until(Transfer(timestamp=Timestamp.now(),
                                        priority=Priority.LOW,
                                        transfer_id=100,
                                        fragmented_payload=[]),
                               monotonic_deadline=loop.time() + 1.0)

    await asyncio.sleep(
        1
    )  # Let all pending tasks finalize properly to avoid stack traces in the output.
Ejemplo n.º 10
0
def _get_run_configs() -> typing.Iterable[RunConfig]:
    """
    Provides interface options to test the demo against.
    When adding new transports, add them to the demo and update this factory accordingly.
    Don't forget about redundant configurations, too.
    """
    from pyuavcan.transport.redundant import RedundantTransport
    from pyuavcan.transport.serial import SerialTransport
    from pyuavcan.transport.udp import UDPTransport

    # UDP
    yield RunConfig(
        demo_env_vars={"DEMO_INTERFACE_KIND": "udp"},
        local_transport_factory=lambda nid: UDPTransport(f"127.0.0.{1 if nid is None else nid}", anonymous=nid is None),
    )

    # Serial
    yield RunConfig(
        demo_env_vars={"DEMO_INTERFACE_KIND": "serial"},
        local_transport_factory=lambda nid: SerialTransport("socket://localhost:50905", local_node_id=nid),
    )

    # DMR UDP+Serial
    def make_udp_serial(nid: typing.Optional[int]) -> pyuavcan.transport.Transport:
        tr = RedundantTransport()
        if nid is not None:
            tr.attach_inferior(UDPTransport(f"127.0.0.{nid}"))
        else:
            tr.attach_inferior(UDPTransport(f"127.0.0.1", anonymous=True))
        tr.attach_inferior(SerialTransport("socket://localhost:50905", local_node_id=nid))
        return tr

    yield RunConfig(
        demo_env_vars={"DEMO_INTERFACE_KIND": "udp_serial"},
        local_transport_factory=make_udp_serial,
    )

    if sys.platform.startswith("linux"):
        from pyuavcan.transport.can.media.socketcan import SocketCANMedia
        from pyuavcan.transport.can import CANTransport

        # CAN
        yield RunConfig(
            demo_env_vars={"DEMO_INTERFACE_KIND": "can"},
            # The demo uses Classic CAN! SocketCAN does not support nonuniform MTU well.
            local_transport_factory=lambda nid: CANTransport(SocketCANMedia("vcan0", 8), local_node_id=nid),
        )

        # TMR CAN
        def make_tmr_can(nid: typing.Optional[int]) -> pyuavcan.transport.Transport:
            from pyuavcan.transport.redundant import RedundantTransport

            tr = RedundantTransport()
            tr.attach_inferior(CANTransport(SocketCANMedia("vcan0", 8), local_node_id=nid))
            tr.attach_inferior(CANTransport(SocketCANMedia("vcan1", 32), local_node_id=nid))
            tr.attach_inferior(CANTransport(SocketCANMedia("vcan2", 64), local_node_id=nid))
            return tr

        yield RunConfig(
            demo_env_vars={"DEMO_INTERFACE_KIND": "can_can_can"},
            local_transport_factory=make_tmr_can,
        )
Ejemplo n.º 11
0
async def _unittest_slow_node(
        compiled: typing.List[pyuavcan.dsdl.GeneratedPackageInfo]) -> None:
    from pyuavcan.application import make_node, make_registry
    import uavcan.primitive
    from uavcan.node import Version_1_0, Heartbeat_1_0, GetInfo_1_0, Mode_1_0, Health_1_0

    asyncio.get_running_loop().slow_callback_duration = 3.0

    assert compiled
    remote_pres = Presentation(UDPTransport("127.1.1.1"))
    remote_hb_sub = remote_pres.make_subscriber_with_fixed_subject_id(
        Heartbeat_1_0)
    remote_info_cln = remote_pres.make_client_with_fixed_service_id(
        GetInfo_1_0, 258)

    trans = RedundantTransport()
    try:
        info = GetInfo_1_0.Response(
            protocol_version=Version_1_0(
                *pyuavcan.UAVCAN_SPECIFICATION_VERSION),
            software_version=Version_1_0(*pyuavcan.__version_info__[:2]),
            name="org.uavcan.pyuavcan.test.node",
        )
        node = make_node(info,
                         make_registry(None, typing.cast(Dict[str, bytes],
                                                         {})),
                         transport=trans)
        print("node:", node)
        assert node.presentation.transport is trans
        node.start()
        node.start()  # Idempotency

        # Check port instantiation API for non-fixed-port-ID types.
        assert "uavcan.pub.optional.id" not in node.registry  # Nothing yet.
        with pytest.raises(KeyError, match=r".*uavcan\.pub\.optional\.id.*"):
            node.make_publisher(uavcan.primitive.Empty_1_0, "optional")
        assert 0xFFFF == int(
            node.registry["uavcan.pub.optional.id"])  # Created automatically!
        with pytest.raises(TypeError):
            node.make_publisher(uavcan.primitive.Empty_1_0)

        # Same but for fixed port-ID types.
        assert "uavcan.pub.atypical_heartbeat.id" not in node.registry  # Nothing yet.
        port = node.make_publisher(uavcan.node.Heartbeat_1_0,
                                   "atypical_heartbeat")
        assert port.port_id == pyuavcan.dsdl.get_model(
            uavcan.node.Heartbeat_1_0).fixed_port_id
        port.close()
        assert 0xFFFF == int(node.registry["uavcan.pub.atypical_heartbeat.id"]
                             )  # Created automatically!
        node.registry[
            "uavcan.pub.atypical_heartbeat.id"] = 111  # Override the default.
        port = node.make_publisher(uavcan.node.Heartbeat_1_0,
                                   "atypical_heartbeat")
        assert port.port_id == 111
        port.close()

        node.heartbeat_publisher.priority = pyuavcan.transport.Priority.FAST
        node.heartbeat_publisher.period = 0.5
        node.heartbeat_publisher.mode = Mode_1_0.MAINTENANCE  # type: ignore
        node.heartbeat_publisher.health = Health_1_0.ADVISORY  # type: ignore
        node.heartbeat_publisher.vendor_specific_status_code = 93
        with pytest.raises(ValueError):
            node.heartbeat_publisher.period = 99.0
        with pytest.raises(ValueError):
            node.heartbeat_publisher.vendor_specific_status_code = -299

        assert node.heartbeat_publisher.priority == pyuavcan.transport.Priority.FAST
        assert node.heartbeat_publisher.period == pytest.approx(0.5)
        assert node.heartbeat_publisher.mode == Mode_1_0.MAINTENANCE
        assert node.heartbeat_publisher.health == Health_1_0.ADVISORY
        assert node.heartbeat_publisher.vendor_specific_status_code == 93

        assert None is await remote_hb_sub.receive_for(2.0)

        assert trans.local_node_id is None
        trans.attach_inferior(UDPTransport("127.1.1.2"))
        assert trans.local_node_id == 258

        for _ in range(2):
            hb_transfer = await remote_hb_sub.receive_for(2.0)
            assert hb_transfer is not None
            hb, transfer = hb_transfer
            assert transfer.source_node_id == 258
            assert transfer.priority == pyuavcan.transport.Priority.FAST
            assert 1 <= hb.uptime <= 9
            assert hb.mode.value == Mode_1_0.MAINTENANCE
            assert hb.health.value == Health_1_0.ADVISORY
            assert hb.vendor_specific_status_code == 93

        info_transfer = await remote_info_cln.call(GetInfo_1_0.Request())
        assert info_transfer is not None
        resp, transfer = info_transfer
        assert transfer.source_node_id == 258
        assert isinstance(resp, GetInfo_1_0.Response)
        assert resp.name.tobytes().decode() == "org.uavcan.pyuavcan.test.node"
        assert resp.protocol_version.major == pyuavcan.UAVCAN_SPECIFICATION_VERSION[
            0]
        assert resp.software_version.major == pyuavcan.__version_info__[0]

        trans.detach_inferior(trans.inferiors[0])
        assert trans.local_node_id is None

        assert None is await remote_hb_sub.receive_for(2.0)

        node.close()
        node.close()  # Idempotency
    finally:
        trans.close()
        remote_pres.close()
        await asyncio.sleep(1.0)  # Let the background tasks terminate.
Ejemplo n.º 12
0
 def one(nid: typing.Optional[int]) -> RedundantTransport:
     red = RedundantTransport()
     red.attach_inferior(UDPTransport("127.0.0.1", local_node_id=nid))
     red.attach_inferior(SerialTransport(VIRTUAL_BUS_URI, nid))
     print("UDP+SERIAL:", red)
     return red
Ejemplo n.º 13
0
 def one(nid: typing.Optional[int]) -> UDPTransport:
     return UDPTransport("127.0.0.1", local_node_id=nid)
Ejemplo n.º 14
0
async def _unittest_slow_node(
    generated_packages: typing.List[pyuavcan.dsdl.GeneratedPackageInfo]
) -> None:
    from pyuavcan.application import Node
    from uavcan.node import Version_1_0, Heartbeat_1_0, GetInfo_1_0, Mode_1_0, Health_1_0

    asyncio.get_running_loop().slow_callback_duration = 3.0

    assert generated_packages
    remote_pres = Presentation(UDPTransport("127.1.1.1"))
    remote_hb_sub = remote_pres.make_subscriber_with_fixed_subject_id(
        Heartbeat_1_0)
    remote_info_cln = remote_pres.make_client_with_fixed_service_id(
        GetInfo_1_0, 258)

    trans = RedundantTransport()
    pres = Presentation(trans)
    try:
        info = GetInfo_1_0.Response(
            protocol_version=Version_1_0(
                *pyuavcan.UAVCAN_SPECIFICATION_VERSION),
            software_version=Version_1_0(*pyuavcan.__version_info__[:2]),
            name="org.uavcan.pyuavcan.test.node",
        )
        node = Node(pres, info, with_diagnostic_subscriber=True)
        print("node:", node)
        assert node.presentation is pres
        node.start()
        node.start()  # Idempotency

        node.heartbeat_publisher.priority = pyuavcan.transport.Priority.FAST
        node.heartbeat_publisher.period = 0.5
        node.heartbeat_publisher.mode = Mode_1_0.MAINTENANCE  # type: ignore
        node.heartbeat_publisher.health = Health_1_0.ADVISORY  # type: ignore
        node.heartbeat_publisher.vendor_specific_status_code = 93
        with pytest.raises(ValueError):
            node.heartbeat_publisher.period = 99.0
        with pytest.raises(ValueError):
            node.heartbeat_publisher.vendor_specific_status_code = -299

        assert node.heartbeat_publisher.priority == pyuavcan.transport.Priority.FAST
        assert node.heartbeat_publisher.period == pytest.approx(0.5)
        assert node.heartbeat_publisher.mode == Mode_1_0.MAINTENANCE
        assert node.heartbeat_publisher.health == Health_1_0.ADVISORY
        assert node.heartbeat_publisher.vendor_specific_status_code == 93

        assert None is await remote_hb_sub.receive_for(2.0)

        assert trans.local_node_id is None
        trans.attach_inferior(UDPTransport("127.1.1.2"))
        assert trans.local_node_id == 258

        for _ in range(2):
            hb_transfer = await remote_hb_sub.receive_for(2.0)
            assert hb_transfer is not None
            hb, transfer = hb_transfer
            assert transfer.source_node_id == 258
            assert transfer.priority == pyuavcan.transport.Priority.FAST
            assert 1 <= hb.uptime <= 9
            assert hb.mode.value == Mode_1_0.MAINTENANCE
            assert hb.health.value == Health_1_0.ADVISORY
            assert hb.vendor_specific_status_code == 93

        info_transfer = await remote_info_cln.call(GetInfo_1_0.Request())
        assert info_transfer is not None
        resp, transfer = info_transfer
        assert transfer.source_node_id == 258
        assert isinstance(resp, GetInfo_1_0.Response)
        assert resp.name.tobytes().decode() == "org.uavcan.pyuavcan.test.node"
        assert resp.protocol_version.major == pyuavcan.UAVCAN_SPECIFICATION_VERSION[
            0]
        assert resp.software_version.major == pyuavcan.__version_info__[0]

        trans.detach_inferior(trans.inferiors[0])
        assert trans.local_node_id is None

        assert None is await remote_hb_sub.receive_for(2.0)

        node.close()
        node.close()  # Idempotency
    finally:
        pres.close()
        remote_pres.close()
        await asyncio.sleep(1.0)  # Let the background tasks terminate.
Ejemplo n.º 15
0
def _unittest_udp_tracer() -> None:
    import socket
    from pytest import approx
    from ipaddress import ip_address
    from pyuavcan.transport import Priority, ServiceDataSpecifier
    from pyuavcan.transport.udp import UDPTransport
    from ._ip import service_data_specifier_to_udp_port

    tr = UDPTransport.make_tracer()
    ts = Timestamp.now()
    ds = ServiceDataSpecifier(11, ServiceDataSpecifier.Role.RESPONSE)

    # VALID SERVICE FRAME
    llp = LinkLayerPacket(
        protocol=socket.AF_INET,
        source=memoryview(b""),
        destination=memoryview(b""),
        payload=memoryview(b"".join([
            # IPv4
            b"\x45\x00",
            (20 + 8 + 24 + 12).to_bytes(
                2,
                "big"),  # Total length (incl. the 20 bytes of the IP header)
            b"\x7e\x50\x40\x00\x40",  # ID, flags, fragment offset, TTL
            b"\x11",  # Protocol (UDP)
            b"\x00\x00",  # IP checksum (unset)
            ip_address("127.0.0.42").packed,  # Source
            ip_address("127.0.0.63").packed,  # Destination
            # UDP/IP
            (12345).to_bytes(2, "big"),  # Source port
            service_data_specifier_to_udp_port(ds).to_bytes(
                2, "big"),  # Destination port
            (8 + 24 + 12).to_bytes(
                2,
                "big"),  # Total length (incl. the 8 bytes of the UDP header)
            b"\x00\x00",  # UDP checksum (unset)
            # UAVCAN/UDP
            b"".join(
                UDPFrame(
                    priority=Priority.SLOW,
                    transfer_id=1234567890,
                    index=0,
                    end_of_transfer=True,
                    payload=memoryview(b"Hello world!"),
                ).compile_header_and_payload()),
        ])),
    )
    ip_packet = IPPacket.parse(llp)
    assert ip_packet is not None
    assert ip_packet.source_destination == (ip_address("127.0.0.42"),
                                            ip_address("127.0.0.63"))
    assert ip_packet.protocol == 0x11
    udp_packet = UDPIPPacket.parse(ip_packet)
    assert udp_packet is not None
    assert udp_packet.source_port == 12345
    assert udp_packet.destination_port == service_data_specifier_to_udp_port(
        ds)
    trace = tr.update(UDPCapture(ts, llp))
    assert isinstance(trace, TransferTrace)
    assert trace.timestamp == ts
    assert trace.transfer_id_timeout == approx(2.0)  # Initial value.
    assert trace.transfer.metadata.transfer_id == 1234567890
    assert trace.transfer.metadata.priority == Priority.SLOW
    assert trace.transfer.metadata.session_specifier.source_node_id == 42
    assert trace.transfer.metadata.session_specifier.destination_node_id == 63
    assert trace.transfer.metadata.session_specifier.data_specifier == ds
    assert trace.transfer.fragmented_payload == [memoryview(b"Hello world!")]

    # ANOTHER TRANSPORT, IGNORED
    assert None is tr.update(pyuavcan.transport.Capture(ts))

    # MALFORMED - UAVCAN/UDP IS EMPTY
    llp = LinkLayerPacket(
        protocol=socket.AF_INET,
        source=memoryview(b""),
        destination=memoryview(b""),
        payload=memoryview(b"".join([
            # IPv4
            b"\x45\x00",
            (20 + 8 + 24 + 12).to_bytes(
                2,
                "big"),  # Total length (incl. the 20 bytes of the IP header)
            b"\x7e\x50\x40\x00\x40",  # ID, flags, fragment offset, TTL
            b"\x11",  # Protocol (UDP)
            b"\x00\x00",  # IP checksum (unset)
            ip_address("127.0.0.42").packed,  # Source
            ip_address("127.0.0.63").packed,  # Destination
            # UDP/IP
            (1).to_bytes(2, "big"),  # Source port
            (1).to_bytes(2, "big"),  # Destination port
            (8).to_bytes(
                2,
                "big"),  # Total length (incl. the 8 bytes of the UDP header)
            b"\x00\x00",  # UDP checksum (unset)
            # UAVCAN/UDP is missing
        ])),
    )
    ip_packet = IPPacket.parse(llp)
    assert ip_packet is not None
    assert ip_packet.source_destination == (ip_address("127.0.0.42"),
                                            ip_address("127.0.0.63"))
    assert ip_packet.protocol == 0x11
    udp_packet = UDPIPPacket.parse(ip_packet)
    assert udp_packet is not None
    assert udp_packet.source_port == 1
    assert udp_packet.destination_port == 1
    assert None is tr.update(UDPCapture(ts, llp))
Ejemplo n.º 16
0
def get_transport(
        node_id: typing.Optional[int]) -> pyuavcan.transport.Transport:
    from pyuavcan.transport.udp import UDPTransport

    return UDPTransport("127.0.0.1", local_node_id=node_id)
Ejemplo n.º 17
0
async def _unittest_udp_transport_ipv4() -> None:
    from pyuavcan.transport import MessageDataSpecifier, ServiceDataSpecifier, PayloadMetadata, Transfer, TransferFrom
    from pyuavcan.transport import Priority, Timestamp, InputSessionSpecifier, OutputSessionSpecifier
    from pyuavcan.transport import ProtocolParameters

    asyncio.get_running_loop().slow_callback_duration = 5.0

    get_monotonic = asyncio.get_event_loop().time

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

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

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

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

    assert tr.loop is asyncio.get_event_loop()
    assert tr.local_node_id == 111
    assert tr2.local_node_id == 222

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

    assert "127.0.0.111" in repr(tr)
    assert tr.protocol_parameters == ProtocolParameters(
        transfer_id_modulo=2 ** 64,
        max_nodes=65535,
        mtu=9000,
    )

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

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

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

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

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

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

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

    subscriber_selective = tr.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), 123), meta)
    assert subscriber_selective is tr.get_input_session(InputSessionSpecifier(MessageDataSpecifier(2345), 123), meta)

    server_listener = tr.get_input_session(
        InputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), None), meta
    )
    assert server_listener is tr.get_input_session(
        InputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), None), meta
    )

    server_responder = tr.get_output_session(
        OutputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 222), meta
    )
    assert server_responder is tr.get_output_session(
        OutputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 222), meta
    )

    client_requester = tr2.get_output_session(
        OutputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111), meta
    )
    assert client_requester is tr2.get_output_session(
        OutputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST), 111), meta
    )

    client_listener = tr2.get_input_session(
        InputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 111), meta
    )
    assert client_listener is tr2.get_input_session(
        InputSessionSpecifier(ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE), 111), meta
    )

    print("tr :", tr.input_sessions, tr.output_sessions)
    assert set(tr.input_sessions) == {subscriber_promiscuous, subscriber_selective, server_listener}
    assert set(tr.output_sessions) == {server_responder}

    print("tr2:", tr2.input_sessions, tr2.output_sessions)
    assert set(tr2.input_sessions) == {client_listener}
    assert set(tr2.output_sessions) == {broadcaster, client_requester}

    assert tr.sample_statistics().received_datagrams[MessageDataSpecifier(2345)].accepted_datagrams == {}
    assert (
        tr.sample_statistics()
        .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST)]
        .accepted_datagrams
        == {}
    )

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

    #
    # Message exchange test.
    #
    assert await broadcaster.send(
        Transfer(
            timestamp=Timestamp.now(), priority=Priority.LOW, transfer_id=77777, fragmented_payload=payload_single
        ),
        monotonic_deadline=get_monotonic() + 5.0,
    )

    rx_transfer = await subscriber_promiscuous.receive(get_monotonic() + 5.0)
    print("PROMISCUOUS SUBSCRIBER TRANSFER:", rx_transfer)
    assert isinstance(rx_transfer, TransferFrom)
    assert rx_transfer.priority == Priority.LOW
    assert rx_transfer.transfer_id == 77777
    assert rx_transfer.fragmented_payload == [b"".join(payload_single)]

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

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

    #
    # Service exchange test.
    #
    assert await client_requester.send(
        Transfer(timestamp=Timestamp.now(), priority=Priority.HIGH, transfer_id=88888, fragmented_payload=payload_x3),
        monotonic_deadline=get_monotonic() + 5.0,
    )

    rx_transfer = await server_listener.receive(get_monotonic() + 5.0)
    print("SERVER LISTENER TRANSFER:", rx_transfer)
    assert isinstance(rx_transfer, TransferFrom)
    assert rx_transfer.priority == Priority.HIGH
    assert rx_transfer.transfer_id == 88888
    assert len(rx_transfer.fragmented_payload) == 3
    assert b"".join(rx_transfer.fragmented_payload) == b"".join(payload_x3)

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

    print("tr :", tr.sample_statistics())
    assert tr.sample_statistics().received_datagrams[MessageDataSpecifier(2345)].accepted_datagrams == {222: 1}
    assert tr.sample_statistics().received_datagrams[
        ServiceDataSpecifier(444, ServiceDataSpecifier.Role.REQUEST)
    ].accepted_datagrams == {
        222: 3 * 2
    }  # Deterministic data loss mitigation is enabled, multiplication factor 2
    print("tr2:", tr2.sample_statistics())
    assert (
        tr2.sample_statistics()
        .received_datagrams[ServiceDataSpecifier(444, ServiceDataSpecifier.Role.RESPONSE)]
        .accepted_datagrams
        == {}
    )

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

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

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

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

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

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

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

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

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

    await asyncio.sleep(1)  # Let all pending tasks finalize properly to avoid stack traces in the output.
Ejemplo n.º 18
0
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
Ejemplo n.º 19
0
async def _unittest_file(
        compiled: typing.List[pyuavcan.dsdl.GeneratedPackageInfo]) -> None:
    from pyuavcan.application import make_node, NodeInfo
    from pyuavcan.transport.udp import UDPTransport
    from pyuavcan.application.file import FileClient, FileServer, Error

    assert compiled
    asyncio.get_running_loop().slow_callback_duration = 3.0

    root_a = mkdtemp(".file", "a.")
    root_b = mkdtemp(".file", "b.")
    srv_node = make_node(
        NodeInfo(name="org.uavcan.pyuavcan.test.file.server"),
        transport=UDPTransport("127.63.0.0",
                               222,
                               service_transfer_multiplier=2),
    )
    cln_node = make_node(
        NodeInfo(name="org.uavcan.pyuavcan.test.file.client"),
        transport=UDPTransport("127.63.0.0",
                               223,
                               service_transfer_multiplier=2),
    )
    try:
        srv_node.start()
        file_server = FileServer(srv_node, [root_a, root_b])
        assert (Path(root_a), Path("abc")) == file_server.locate(Path("abc"))
        assert [] == list(file_server.glob("*"))

        cln_node.start()
        cln = FileClient(cln_node, 222)

        async def ls(path: str) -> typing.List[str]:
            out: typing.List[str] = []
            async for e in cln.list(path):
                out.append(e)
            return out

        assert [] == await ls("")
        assert [] == await ls("nonexistent/directory")
        assert (await cln.get_info("none")).error.value == Error.NOT_FOUND

        assert 0 == await cln.touch("a/foo/x")
        assert 0 == await cln.touch("a/foo/y")
        assert 0 == await cln.touch("b")
        assert ["foo"] == await ls("a")

        # Make sure files are created.
        assert [
            (file_server.roots[0], Path("a/foo/x")),
            (file_server.roots[0], Path("a/foo/y")),
        ] == list(sorted(file_server.glob("a/foo/*")))

        assert await cln.read("a/foo/x") == b""
        assert await cln.read(
            "/a/foo/x") == b""  # Slash or no slash makes no difference.
        assert await cln.read("a/foo/z") == Error.NOT_FOUND
        assert (await cln.get_info("a/foo/z")).error.value == Error.NOT_FOUND

        # Write non-existent file
        assert await cln.write("a/foo/z",
                               bytes(range(200)) * 3) == Error.NOT_FOUND

        # Write into empty file
        assert await cln.write("a/foo/x", bytes(range(200)) * 3) == 0
        assert await cln.read("a/foo/x") == bytes(range(200)) * 3
        assert (await cln.get_info("a/foo/x")).size == 600

        # Truncation -- this write is shorter
        hundred = bytes(x ^ 0xFF for x in range(100))
        assert await cln.write("a/foo/x", hundred * 4) == 0
        assert (await cln.get_info("a/foo/x")).size == 400
        assert await cln.read("a/foo/x") == (hundred * 4)
        assert (await cln.get_info("a/foo/x")).size == 400

        # Fill in the middle without truncation
        ref = bytearray(hundred * 4)
        for i in range(100):
            ref[i + 100] = 0x55
        assert len(ref) == 400
        assert (await cln.get_info("a/foo/x")).size == 400
        assert await cln.write("a/foo/x",
                               b"\x55" * 100,
                               offset=100,
                               truncate=False) == 0
        assert (await cln.get_info("a/foo/x")).size == 400
        assert await cln.read("a/foo/x") == ref

        # Fill in the middle with truncation
        assert await cln.write("a/foo/x", b"\xAA" * 50, offset=50) == 0
        assert (await cln.get_info("a/foo/x")).size == 100
        assert await cln.read("a/foo/x") == hundred[:50] + b"\xAA" * 50

        # Directories
        info = await cln.get_info("a/foo")
        print("a/foo:", info)
        assert info.error.value == 0
        assert info.is_writeable
        assert info.is_readable
        assert not info.is_file_not_directory
        assert not info.is_link

        assert (await
                cln.get_info("a/foo/nothing")).error.value == Error.NOT_FOUND
        assert await cln.write("a/foo", b"123") in (Error.IS_DIRECTORY,
                                                    Error.ACCESS_DENIED
                                                    )  # Windows compatibility

        # Removal
        assert (await cln.remove("a/foo/z")) == Error.NOT_FOUND
        assert (await cln.remove("a/foo/x")) == 0
        assert (await cln.touch("a/foo/x")) == 0  # Put it back
        assert (await cln.remove("a/foo/")) == 0  # Removed
        assert (await cln.remove("a/foo/")) == Error.NOT_FOUND  # Not found

        # Copy
        assert (await cln.touch("r/a")) == 0
        assert (await cln.touch("r/b/0")) == 0
        assert (await cln.touch("r/b/1")) == 0
        assert not (await cln.get_info("r/b")).is_file_not_directory
        assert ["a", "b"] == await ls("r")
        assert (await cln.copy("r/b", "r/c")) == 0
        assert ["a", "b", "c"] == await ls("r")
        assert (await cln.copy("r/a", "r/c")) != 0  # Overwrite not enabled
        assert ["a", "b", "c"] == await ls("r")
        assert not (await cln.get_info("r/c")).is_file_not_directory
        assert (await cln.copy("/r/a", "r/c", overwrite=True)) == 0
        assert (await cln.get_info("r/c")).is_file_not_directory

        # Move
        assert ["a", "b", "c"] == await ls("r")
        assert (await cln.move("/r/a", "r/c")) != 0  # Overwrite not enabled
        assert (await cln.move("/r/a", "r/c", overwrite=True)) == 0
        assert ["b", "c"] == await ls("r")
        assert (await cln.move("/r/a", "r/c",
                               overwrite=True)) == Error.NOT_FOUND
        assert ["b", "c"] == await ls("r")

        # Access protected files
        if sys.platform.startswith("linux"):  # pragma: no branch
            file_server.roots.append(Path("/"))
            info = await cln.get_info("dev/null")
            print("/dev/null:", info)
            assert info.error.value == 0
            assert not info.is_link
            assert info.is_writeable
            assert info.is_file_not_directory

            info = await cln.get_info("/bin/sh")
            print("/bin/sh:", info)
            assert info.error.value == 0
            assert not info.is_writeable
            assert info.is_file_not_directory

            assert await cln.read("/dev/null",
                                  size=100) == b""  # Read less than requested
            assert await cln.read(
                "/dev/zero",
                size=100) == b"\x00" * 256  # Read more than requested
            assert await cln.write("bin/sh", b"123") == Error.ACCESS_DENIED

            file_server.roots.pop(-1)
    finally:
        srv_node.close()
        cln_node.close()
        await asyncio.sleep(1.0)
        shutil.rmtree(root_a, ignore_errors=True)
        shutil.rmtree(root_b, ignore_errors=True)
Ejemplo n.º 20
0
def get_transport(
        node_id: typing.Optional[int]) -> pyuavcan.transport.Transport:
    from pyuavcan.transport.udp import UDPTransport
    return UDPTransport(
        f'127.0.0.{node_id}/8') if node_id is not None else UDPTransport(
            '127.255.255.255/8')
Ejemplo n.º 21
0
 def one(nid: typing.Optional[int]) -> UDPTransport:
     return UDPTransport(
         f'127.0.0.{nid}/8') if nid is not None else UDPTransport(
             '127.255.255.255/8')