Exemplo n.º 1
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     LoggingMixin.__init__(self,
                           logger=logging.getLogger("serial"),
                           extra_func=partial(str, f"[Serial Writer {self.ser.name}]"))
     self.unsent = deque(maxlen=20)
     self.retry_backoff = BackoffGenerator(0.100, 1.2, 1.000)
Exemplo n.º 2
0
def awaiting_release_handler(circuit: NetRomCircuit, event: NetRomStateEvent,
                             netrom: NetRom,
                             logger: LoggingMixin) -> NetRomStateType:
    assert circuit.state == NetRomStateType.AwaitingRelease

    if event.event_type == NetRomEventType.NETROM_DISCONNECT_ACK:
        if event.packet.circuit_idx == circuit.circuit_idx and event.packet.circuit_id == circuit.circuit_id:
            netrom.nl_disconnect_indication(circuit.circuit_idx,
                                            circuit.circuit_id,
                                            circuit.remote_call,
                                            circuit.local_call)
            return NetRomStateType.Disconnected
        else:
            logger.debug("Invalid disconnect ack. Disconnecting anyways")
            return NetRomStateType.Disconnected
    elif event.event_type == NetRomEventType.NETROM_DISCONNECT:
        disc_ack = NetRomPacket(
            event.packet.source,
            event.packet.dest,
            7,  # TODO configure TTL
            event.packet.circuit_idx,
            event.packet.circuit_id,
            0,  # Our circuit idx
            0,  # Our circuit id
            OpType.DisconnectAcknowledge.as_op_byte(False, False, False))
        netrom.write_packet(disc_ack)
        netrom.nl_disconnect_indication(circuit.circuit_idx,
                                        circuit.circuit_id,
                                        circuit.remote_call,
                                        circuit.local_call)
        circuit.timer.cancel()
        return NetRomStateType.Disconnected
    else:
        # TODO handle any other cases differently?
        return NetRomStateType.AwaitingRelease
Exemplo n.º 3
0
    def __init__(self, config: PortConfig, link_port: int, link_call: AX25Call,
                 scheduler: Scheduler, queue: L2Queuing,
                 link_multiplexer: DefaultLinkMultiplexer,
                 l3_protocols: L3Protocols, intercept_calls: Set[AX25Call],
                 interceptor: Callable[[FrameData], None]):
        self.config = config
        self.link_port = link_port
        self.link_call = link_call
        self.queue = queue  # l2 buffer
        self.link_multiplexer = link_multiplexer
        self.l3_protocols = l3_protocols
        self.intercept_calls = intercept_calls
        self.interceptor = interceptor

        # Mapping of link_id to AX25 address. When we establish new logical links,
        # add them here so we can properly address payloads from L3
        self.links_by_id: Dict[int, AX25Call] = dict()
        self.links_by_address: Dict[AX25Call, int] = dict()

        self.state_machine = AX25StateMachine(self, scheduler.timer)
        self.link_multiplexer.register_device(self)

        self.cq_timer = scheduler.timer(300_000, self._send_cq, True)
        scheduler.timer(5_000, self.state_machine.start, True)

        def extra():
            return f"[L2 AX25 Port={self.link_port} Call={str(self.link_call)}]"

        LoggingMixin.__init__(self, extra_func=extra)
Exemplo n.º 4
0
    def __init__(self,
                 node_call: AX25Call,
                 node_alias: str,
                 scheduler: Scheduler,
                 link_multiplexer: LinkMultiplexer,
                 routing_table):
        LoggingMixin.__init__(self)
        self.node_call = node_call
        self.node_alias = node_alias
        self.link_multiplexer = link_multiplexer
        self.router = routing_table
        self.nodes_timer = scheduler.timer(60000.0, self.broadcast_nodes)
        self.netrom_transport = None
        self.nodes_lock = threading.Lock()

        # Register our-calls with routing table
        for l2 in link_multiplexer.l2_devices.values():
            l2_address = l2.get_link_address()
            if isinstance(l2_address, AX25Address):
                self.router.our_calls.add(cast(AX25Address, l2_address).to_ax25_call())

        now = datetime.datetime.now()
        prune_intervals = round((now - self.router.updated_at).seconds / 60.000)
        if prune_intervals < 10:
            self.info(f"Pruning routes {prune_intervals} times")
            for i in range(prune_intervals):
                self.router.prune_routes()
        else:
            self.info("Routes too old, discarding.")
            self.router.clear_routes()

        self.nodes_timer.start()
        self.broadcast_nodes()
Exemplo n.º 5
0
    def __init__(self, config: NetworkConfig, loop: AbstractEventLoop):
        self.config = config
        self.sm = NetRomStateMachine(self, AsyncioTimer)
        self.router = NetRomRoutingTable(config.node_alias())
        self.l3_apps: Dict[AX25Call, str] = {}

        # Mapping of destination addresses to protocol factory. When a new connection is made to the destination
        # we will create a new instance of the protocol as well as a NetRom transport and add it to l3_connections
        self.l3_servers: Dict[AX25Call, Callable[[], Protocol]] = {}

        # This is a mapping of local circuit IDs to (transport, protocol). When incoming data is handled for a
        # circuit, this is how we pass it on to the instance of the protocol
        self.l3_connections: Dict[int, Tuple[Transport, Protocol]] = {}

        # This is a mapping of local circuit ID to a protocol factory. These protocols do not yet have a transport and
        # are in a half-opened state. Once a connect ack is received, a transport will be created and these will be
        # migrated to l3_connections
        self.l3_half_open: Dict[int, Callable[[], Protocol]] = {}

        self.data_links: Dict[int, DataLinkManager] = {}
        self.route_lock = Lock()
        self.loop = loop

        def extra():
            return f"[L4 Call={str(config.node_call())} Alias={config.node_alias()}]"

        LoggingMixin.__init__(self, logging.getLogger("main"), extra)
Exemplo n.º 6
0
 def __init__(self, queue: L3Queueing, l2: L2Protocol):
     CloseableThreadLoop.__init__(
         self, name=f"L3-to-L2 for Port={l2.get_device_id()}")
     LoggingMixin.__init__(self)
     self.queue = queue
     self.l2 = l2
     self.retry_backoff = BackoffGenerator(0.500, 1.5, 3.000)
Exemplo n.º 7
0
 def __init__(self, network_app: NetworkApp, environ: Dict[str, Any]):
     LoggingMixin.__init__(self)
     self.environ = environ
     self.network_app = network_app
     self.transport = None
     self.closed = False
     self.unpacker = msgpack.Unpacker()
Exemplo n.º 8
0
    def __init__(self, protocol: IOProtocol, device_name: str, speed: int,
                 timeout: float, scheduler: Scheduler):
        LoggingMixin.__init__(self)
        CloseableThreadLoop.__init__(self, f"Serial Device {device_name}")

        self._scheduler = scheduler
        self._device_name = device_name
        self._protocol = protocol
        self._ser = serial.Serial(port=None,
                                  baudrate=speed,
                                  timeout=timeout,
                                  write_timeout=timeout)
        self._ser.port = device_name
        self._closed_latch = CountDownLatch(2)
        self._open_event = threading.Event()
        self._error_event = threading.Event()
        self._open_backoff = BackoffGenerator(0.100, 1.2, 5.000)
        # Submit the reader and writer threads first, so they will be shutdown first
        self._scheduler.submit(
            SerialReadLoop(f"Serial Reader {self._ser.name}", self._ser,
                           self._protocol, self._open_event, self._error_event,
                           self._closed_latch))
        self._scheduler.submit(
            SerialWriteLoop(f"Serial Writer {self._ser.name}", self._ser,
                            self._protocol, self._open_event,
                            self._error_event, self._closed_latch))
        self._scheduler.submit(self)
Exemplo n.º 9
0
 def __init__(self, l3_protocol: L3Protocol):
     LoggingMixin.__init__(self)
     self.l3_protocol = l3_protocol
     self.connections: Dict[int, Tuple[Transport, DProtocol]] = dict()
     self.l3_protocol.register_transport_protocol(self)
     self.broadcast_protocol: Optional[BroadcastProtocol] = None
     self.datagram_protocol: Optional[DatagramProtocol] = None
Exemplo n.º 10
0
 def __init__(self, path: str, protocol: Protocol):
     CloseableThread.__init__(self, f"Unix Server {path}")
     LoggingMixin.__init__(self)
     self.path = path
     self.protocol = protocol
     self.server: Optional[Server] = None
     self._loop: Optional[AbstractEventLoop] = None
Exemplo n.º 11
0
    def __init__(self, config: NetworkConfig, l3_protocol: L3Protocol,
                 scheduler: Scheduler):
        self.config = config
        self.sm = NetRomStateMachine(self, scheduler.timer)
        # Mapping of destination addresses to protocol factory. When a new connection is made to the destination
        # we will create a new instance of the protocol as well as a NetRom transport and add it to l3_connections
        self.l3_servers: Dict[AX25Call, Callable[[], Protocol]] = {}

        # This is a mapping of local circuit IDs to (transport, protocol). When incoming data is handled for a
        # circuit, this is how we pass it on to the instance of the protocol
        self.l3_connections: Dict[int, Tuple[Transport, Protocol]] = {}

        # This is a mapping of local circuit ID to a protocol factory. These protocols do not yet have a transport and
        # are in a half-opened state. Once a connect ack is received, a transport will be created and these will be
        # migrated to l3_connections
        self.l3_half_open: Dict[int, Protocol] = {}

        self.l3_destinations: Set[AX25Call] = set()

        self.l3_protocol = l3_protocol
        self.l3_protocol.register_transport_protocol(self)

        def extra():
            return f"[L4]"

        LoggingMixin.__init__(self, logging.getLogger("main"), extra)
Exemplo n.º 12
0
 def __init__(self, network: L3Protocol, datagram_manager: L4Protocol,
              fragment_protocol: FragmentProtocol,
              reliable_protocol: ReliableProtocol):
     LoggingMixin.__init__(self)
     self.network = network
     self.datagram_manager = datagram_manager
     self.fragment_protocol = fragment_protocol
     self.reliable_protocol = reliable_protocol
Exemplo n.º 13
0
    def __init__(self, network: MeshProtocol, transport: L4Protocol,
                 reliable_manager: ReliableManager):
        LoggingMixin.__init__(self)
        self.network = network
        self.transport = transport
        self.reliable_manager = reliable_manager

        self.send_seq: int = 1
        self.seq_lock = threading.Lock()

        self.ttl_cache = TTLCache(WallTime(), 30_000)
Exemplo n.º 14
0
 def __init__(self, host: str, port: int, port_mapping: Dict[AX25Call, int],
              port_queues: Dict[int, L2Queuing], writer: UDPWriter):
     CloseableThread.__init__(self, f"UDP {host}:{port}")
     LoggingMixin.__init__(self, udp_logger)
     self.host = host
     self.port = port
     self.port_mapping = port_mapping
     self.port_queues = port_queues
     self.writer = writer
     self._loop: Optional[AbstractEventLoop] = None
     self._transport = None
Exemplo n.º 15
0
    def __init__(self, network: L3Protocol, datagram_manager: L4Protocol):
        LoggingMixin.__init__(self)
        self.network = network
        self.datagram_manager = datagram_manager

        self.send_seq: int = 1
        self.seq_lock = threading.Lock()

        # A buffer of undelivered fragment. Structured like source -> sequence -> fragments
        self.buffer: Dict[MeshAddress, Dict[
            int, List[Fragment]]] = defaultdict(lambda: defaultdict(list))
Exemplo n.º 16
0
 def __init__(self, app_name: str, app_call: AX25Call, app_alias: str,
              transport_manager: NetRomTransportProtocol,
              multiplexer: TransportMultiplexer):
     Protocol.__init__(self)
     LoggingMixin.__init__(self)
     self.app_name = app_name
     self.app_call = app_call
     self.app_alias = app_alias
     self.transport_manager = transport_manager
     self.multiplexer = multiplexer
     self.unpacker = msgpack.Unpacker()
     self.out_queue = queue.Queue()
Exemplo n.º 17
0
 def __init__(self, app_name: str, app_alias: str, app_bind_address: str,
              transport_manager: L4Protocol,
              multiplexer: TransportMultiplexer):
     Protocol.__init__(self)
     LoggingMixin.__init__(self)
     self.app_name = app_name
     self.app_alias = app_alias
     self.app_bind_address = app_bind_address
     self.transport_manager = transport_manager
     self.multiplexer = multiplexer
     self.unpacker = msgpack.Unpacker()
     self.out_queue = queue.Queue()
Exemplo n.º 18
0
    def __init__(self, queue_size, max_payload_size):
        LoggingMixin.__init__(self)
        self._max_payload_size = max_payload_size
        self._outbound = queue.Queue(maxsize=queue_size)

        # Ring buffer for decoded inbound messages
        self._inbound = deque(maxlen=queue_size)
        self._inbound_count = itertools.count()
        self._last_inbound = next(self._inbound_count)

        self._lock: threading.Lock = threading.Lock()
        self._not_empty: threading.Condition = threading.Condition(self._lock)
Exemplo n.º 19
0
    def __init__(self, config: NetworkConfig, link: LinkMultiplexer,
                 network: MeshProtocol,
                 transport_manager: MeshTransportManager,
                 scheduler: Scheduler):
        self.config = config
        self.l3 = network
        self.l2s = link
        self.l4 = transport_manager
        self.scheduler = scheduler
        self.transport: Optional[Transport] = None
        self.client_transport: Optional[Transport] = None
        self.pending_open: Optional[AX25Call] = None

        self.parser = argparse.ArgumentParser(prog="TARPN", add_help=False)
        sub_parsers = self.parser.add_subparsers(title="command",
                                                 required=True,
                                                 dest="command")

        sub_parsers.add_parser("help", description="Print this help")
        sub_parsers.add_parser("bye", description="Disconnect from this node")
        sub_parsers.add_parser("whoami", description="Print the current user")
        sub_parsers.add_parser("hostname",
                               description="Print the current host")
        sub_parsers.add_parser("routes",
                               description="Print the current routing table")
        sub_parsers.add_parser(
            "nodes", description="Print the nodes in the routing table")

        port_parser = sub_parsers.add_parser(
            "ports", description="List available ports")
        port_parser.add_argument("--verbose", "-v", action="store_true")

        link_parser = sub_parsers.add_parser("links",
                                             description="Show existing links")
        link_parser.add_argument("--verbose", "-v", action="store_true")

        connect_parser = sub_parsers.add_parser(
            "connect", description="Connect to a remote station")
        connect_parser.add_argument("dest",
                                    type=str,
                                    help="Destination callsign to connect to")

        def extra():
            if self.transport:
                (host, port) = self.transport.get_extra_info('peername')
                return f"[Admin {host}:{port}]"
            else:
                return ""

        LoggingMixin.__init__(self, extra_func=extra)
        self.info("Created NodeCommandProcessor")
Exemplo n.º 20
0
    def __init__(self, network: MeshProtocol, scheduler: Scheduler):
        LoggingMixin.__init__(self)
        self.network = network
        self.scheduler = scheduler

        self.mutex = threading.Lock()
        self.not_empty = threading.Condition(self.mutex)
        self.not_full = threading.Condition(self.mutex)

        self.sent: deque[ReliableItem] = deque()
        self.timer = scheduler.timer(3_000, self.check_acks)
        self.backoff = backoff(1_000, 1.2, 5_000)

        self.seq_by_neighbor: Dict[MeshAddress, int] = defaultdict(int)
Exemplo n.º 21
0
    def __init__(self, link_call: AX25Call, link_port: int,
                 inbound: asyncio.Queue, outbound: asyncio.Queue,
                 future_provider: Callable[[], Future]):
        self.link_call = link_call
        self.link_port = link_port
        self.inbound = inbound  # PortFrame
        self.outbound = outbound  # PortFrame
        self.state_machine = AX25StateMachine(self, AsyncioTimer)
        self.l3_handlers: List[L3Handler] = []
        self.future_provider = future_provider
        self._stopped: bool = False

        def extra():
            return f"[L2 Port={self.link_port} Call={str(self.link_call)}]"

        LoggingMixin.__init__(self, logging.getLogger("main"), extra)
Exemplo n.º 22
0
def awaiting_connection_handler(circuit: NetRomCircuit,
                                event: NetRomStateEvent, netrom: NetRom,
                                logger: LoggingMixin) -> NetRomStateType:
    assert circuit.state == NetRomStateType.AwaitingConnection

    if event.event_type == NetRomEventType.NETROM_CONNECT:
        return NetRomStateType.AwaitingConnection
    elif event.event_type == NetRomEventType.NETROM_CONNECT_ACK:
        ack = cast(NetRomConnectAck, event.packet)
        if ack.circuit_idx == circuit.circuit_idx and ack.circuit_id == circuit.circuit_id:
            circuit.remote_circuit_idx = ack.tx_seq_num
            circuit.remote_circuit_id = ack.rx_seq_num
            circuit.window_size = ack.accept_window_size
            netrom.nl_connect_indication(
                circuit.circuit_idx, circuit.circuit_id, circuit.remote_call,
                circuit.local_call, circuit.origin_node, circuit.origin_user)
            circuit.timer.reset()
            return NetRomStateType.Connected
        else:
            logger.debug("Unexpected circuit id in connection ack")
            return NetRomStateType.AwaitingConnection
    elif event.event_type in (NetRomEventType.NETROM_DISCONNECT,
                              NetRomEventType.NETROM_DISCONNECT_ACK,
                              NetRomEventType.NETROM_INFO,
                              NetRomEventType.NETROM_INFO_ACK):
        return NetRomStateType.AwaitingConnection
    elif event.event_type == NetRomEventType.NL_CONNECT:
        conn = NetRomConnectRequest(
            circuit.remote_call,
            circuit.local_call,
            7,  # TODO configure TTL
            circuit.circuit_idx,
            circuit.circuit_id,
            0,  # Unused
            0,  # Unused
            OpType.ConnectRequest.as_op_byte(False, False, False),
            2,  # TODO get this from config
            circuit.origin_user,  # Origin user
            circuit.origin_node  # Origin node
        )
        netrom.write_packet(conn)
        circuit.timer.reset()
        return NetRomStateType.AwaitingConnection
    elif event.event_type in (NetRomEventType.NL_DISCONNECT,
                              NetRomEventType.NL_DATA):
        return NetRomStateType.AwaitingConnection
Exemplo n.º 23
0
    def __init__(self, time: Time, config: NetworkConfig,
                 link_multiplexer: LinkMultiplexer, l4_handlers: L4Handlers,
                 scheduler: Scheduler):
        LoggingMixin.__init__(self, extra_func=self.log_ident)
        CloseableThreadLoop.__init__(self, name="MeshNetwork")

        self.time = time
        self.config = config
        self.link_multiplexer = link_multiplexer
        self.l4_handlers = l4_handlers
        self.scheduler = scheduler

        self.queue = queue.Queue()
        self.our_address = MeshAddress.parse(config.get("mesh.address"))
        self.host_name = config.get("host.name")
        self.ping_protocol = PingProtocol(self)

        # TTL cache of seen frames from each source
        self.header_cache: TTLCache = TTLCache(time, 30_000)

        # Our own send sequence
        self.send_seq: int = 1
        self.seq_lock = threading.Lock()

        # Link states and neighbors
        self.neighbors: Dict[MeshAddress, Neighbor] = dict()

        # An epoch for our own link state changes. Any time a neighbor comes or goes, or the quality changes,
        # we increment this counter. Uses a "lollipop sequence" to allow for easy detection of wrap around vs
        # reset
        self.our_link_state_epoch_generator = lollipop_sequence()
        self.our_link_state_epoch: int = next(
            self.our_link_state_epoch_generator)

        # Epochs we have received from other nodes and their link states
        self.link_state_epochs: Dict[MeshAddress, int] = dict()
        self.host_names: Dict[MeshAddress, str] = dict()
        self.link_states: Dict[MeshAddress, List[LinkStateHeader]] = dict()

        self.last_hello_time = datetime.fromtimestamp(0)
        self.last_advert = datetime.utcnow()
        self.last_query = datetime.utcnow()
        self.last_epoch_bump = datetime.utcnow()

        self.scheduler.timer(1_000, partial(self.scheduler.submit, self), True)
Exemplo n.º 24
0
    def __init__(self, settings: Settings, links: List[DataLinkManager],
                 network: NetRom):
        self.settings: Settings = settings
        self.transport: Optional[Transport] = None
        self.client_transport: Optional[NetworkTransport] = None
        self.datalinks: List[DataLinkManager] = links
        self.network: NetRom = network
        self.pending_open: Optional[AX25Call] = None

        parser = argparse.ArgumentParser(prog="TARPN", add_help=False)
        sub_parsers = parser.add_subparsers(title="command",
                                            required=True,
                                            dest="command")

        sub_parsers.add_parser("help", description="Print this help")
        sub_parsers.add_parser("bye", description="Disconnect from this node")
        sub_parsers.add_parser("whoami", description="Print the current user")
        sub_parsers.add_parser("hostname",
                               description="Print the current host")

        port_parser = sub_parsers.add_parser(
            "ports", description="List available ports")
        port_parser.add_argument("--verbose", "-v", action="store_true")

        link_parser = sub_parsers.add_parser("links",
                                             description="Show existing links")
        link_parser.add_argument("--verbose", "-v", action="store_true")

        connect_parser = sub_parsers.add_parser(
            "connect", description="Connect to a remote station")
        connect_parser.add_argument("dest",
                                    type=str,
                                    help="Destination callsign to connect to")

        self.parser = parser

        def extra():
            if self.transport:
                (host, port) = self.transport.get_extra_info('peername')
                return f"[Admin {host}:{port}]"
            else:
                return ""

        LoggingMixin.__init__(self, logging.getLogger("main"), extra)
        self.info("Created CommandProcessorProtocol")
Exemplo n.º 25
0
    def __init__(self,
                 port_id: int,
                 queue: L2IOQueuing,
                 check_crc: bool = False,
                 hdlc_port: int = 0):
        self.port_id = port_id
        self.check_crc = check_crc
        self.hdlc_port = hdlc_port

        self.msgs_recvd = 0
        self.in_frame = False
        self.saw_fend = False

        self._l2_queue: L2IOQueuing = queue
        self._send_immediate: Optional[bytes] = None
        self._buffer = bytearray()

        self.closed = False
        LoggingMixin.__init__(self, logging.getLogger("main"))
Exemplo n.º 26
0
    def __init__(self, link_port: int, link_call: AX25Call,
                 scheduler: Scheduler, queue: L2Queuing,
                 link_multiplexer: LinkMultiplexer, l3_protocols: L3Protocols):
        self.link_port = link_port
        self.link_call = link_call
        self.queue = queue  # l2 buffer
        self.link_multiplexer = link_multiplexer
        self.l3_protocols = l3_protocols

        # Mapping of link_id to AX25 address. When we establish new logical links,
        # add them here so we can properly address payloads from L3
        self.links_by_id: Dict[int, AX25Call] = dict()
        self.links_by_address: Dict[AX25Call, int] = dict()

        self.state_machine = AX25StateMachine(self, scheduler.timer)
        self.link_multiplexer.register_device(self)

        def extra():
            return f"[L2 AX25 Port={self.link_port} Call={str(self.link_call)}]"

        LoggingMixin.__init__(self, logging.getLogger("main"), extra)
Exemplo n.º 27
0
 def _handle_event(self, event: NetRomStateEvent):
     if event is not None:
         circuit = self._get_circuit(event.circuit_id, event.circuit_id)
         handler = self._handlers[circuit.state]
         if handler is None:
             raise RuntimeError(f"No handler for state {handler}")
         logger = LoggingMixin(self._logger, circuit.log_prefix)
         try:
             new_state = handler(circuit, event, self._netrom, logger)
             circuit.state = new_state
             logger.info(f"Handled {event}")
         except Exception:
             logger.exception(f"Failed to handle {event}")
Exemplo n.º 28
0
def connected_handler(circuit: NetRomCircuit, event: NetRomStateEvent,
                      netrom: NetRom, logger: LoggingMixin) -> NetRomStateType:
    assert circuit.state == NetRomStateType.Connected

    if event.event_type == NetRomEventType.NETROM_CONNECT:
        connect_req = cast(NetRomConnectRequest, event.packet)
        if connect_req.circuit_idx == circuit.circuit_idx and connect_req.circuit_id == circuit.circuit_id:
            # Treat this as a reconnect and ack it
            connect_ack = NetRomConnectAck(
                connect_req.source,
                connect_req.dest,
                7,  # TODO get TTL from config
                connect_req.circuit_idx,
                connect_req.circuit_id,
                circuit.circuit_idx,
                circuit.circuit_id,
                OpType.ConnectAcknowledge.as_op_byte(False, False, False),
                connect_req.proposed_window_size)
            netrom.write_packet(connect_ack)
            netrom.nl_connect_indication(
                circuit.circuit_idx, circuit.circuit_id, circuit.remote_call,
                circuit.local_call, circuit.origin_node, circuit.origin_user)
            circuit.timer.reset()
            return NetRomStateType.Connected
        else:
            # Reject this and disconnect
            logger.debug(
                "Rejecting connect request due to invalid circuit ID/IDX")
            connect_rej = NetRomConnectAck(
                connect_req.source,
                connect_req.dest,
                7,  # TODO get TTL from config
                connect_req.circuit_idx,
                connect_req.circuit_id,
                circuit.circuit_idx,
                circuit.circuit_id,
                OpType.ConnectAcknowledge.as_op_byte(True, False, False),
                connect_req.proposed_window_size)
            netrom.write_packet(connect_rej)
            netrom.nl_disconnect_indication(circuit.circuit_idx,
                                            circuit.circuit_id,
                                            circuit.remote_call,
                                            circuit.local_call)
            circuit.timer.cancel()
            return NetRomStateType.Disconnected
    elif event.event_type == NetRomEventType.NETROM_CONNECT_ACK:
        connect_ack = cast(NetRomConnectAck, event.packet)
        if connect_ack.tx_seq_num == circuit.remote_circuit_idx and \
                connect_ack.rx_seq_num == circuit.remote_circuit_id and \
                connect_ack.circuit_idx == circuit.circuit_idx and \
                connect_ack.circuit_id == circuit.circuit_id:
            netrom.nl_connect_indication(
                circuit.circuit_idx, circuit.circuit_id, circuit.remote_call,
                circuit.local_call, circuit.origin_node, circuit.origin_user)
            circuit.timer.reset()
            return NetRomStateType.Connected
        else:
            #  TODO what now?
            return NetRomStateType.Connected
    elif event.event_type == NetRomEventType.NETROM_DISCONNECT:
        disc_ack = NetRomPacket(
            event.packet.source,
            event.packet.dest,
            7,  # TODO configure TTL
            circuit.remote_circuit_id,
            circuit.remote_circuit_id,
            0,  # Not used
            0,  # Not used
            OpType.DisconnectAcknowledge.as_op_byte(False, False, False))
        netrom.write_packet(disc_ack)
        netrom.nl_disconnect_indication(circuit.circuit_idx,
                                        circuit.circuit_id,
                                        circuit.remote_call,
                                        circuit.local_call)
        circuit.timer.cancel()
        return NetRomStateType.Disconnected
    elif event.event_type == NetRomEventType.NETROM_DISCONNECT_ACK:
        logger.debug("Unexpected disconnect ack in connected state!")
        return NetRomStateType.Disconnected
    elif event.event_type == NetRomEventType.NETROM_INFO:
        """
        The TX number from the INFO packet is the current sequence number while the the RX number is the next
        expected sequence number on the other end of the circuit. This serves as a mechanism to acknowledge
        previous INFO without sending an explicit ACK
        """
        info = cast(NetRomInfo, event.packet)
        if info.tx_seq_num == circuit.recv_state():
            # We got the message number we expected
            circuit.inc_recv_state()
            circuit.enqueue_info_ack(netrom)
            circuit.more += info.info
            if not info.more_follows():
                # TODO expire old more-follows data
                netrom.nl_data_indication(circuit.circuit_idx,
                                          circuit.circuit_id,
                                          circuit.remote_call,
                                          circuit.local_call, circuit.more)
                circuit.more = bytearray()
        elif info.tx_seq_num < circuit.recv_state():
            # Possible retransmission of previous message, ignore?
            pass
        else:
            # Got a higher number than expected, we missed something, ask the sender to rewind
            # to our last confirmed state
            nak = NetRomPacket(
                info.source,
                info.dest,
                7,  # TODO config
                circuit.remote_circuit_idx,
                circuit.remote_circuit_id,
                0,  # Unused
                circuit.recv_state(),
                OpType.InformationAcknowledge.as_op_byte(False, True, False))
            netrom.write_packet(nak)

        # Handle the ack logic
        if info.rx_seq_num > circuit.hw:
            for seq in range(info.rx_seq_num, circuit.hw + 1):
                seq = (seq + 127) % 128
                circuit.info_futures[seq].set_result(True)
            circuit.hw = info.rx_seq_num
        else:
            # Out of sync, error
            pass

        if info.rx_seq_num == circuit.send_state():
            # We are in-sync, all is well
            pass
        elif info.rx_seq_num < circuit.send_state():
            # Other side is lagging
            pass
        else:
            # Other side has ack'd something out of range, raise an error
            pass

        # Handle the other flags
        if info.choke():
            # TODO stop sending until further notice
            pass
        if info.nak():
            seq_resend = event.packet.rx_seq_num
            logger.warning(f"Got Info NAK, rewinding to {seq_resend}")
            while seq_resend < circuit.send_state():
                info_to_resend = circuit.sent_info[seq_resend]
                info_to_resend.rx_seq_num = circuit.recv_state()
                netrom.write_packet(info_to_resend)
                seq_resend += 1

        return NetRomStateType.Connected
    elif event.event_type == NetRomEventType.NETROM_INFO_ACK:
        """
        If the choke flag is set (bit 7 of the opcode byte), it indicates that this node cannot accept any more 
        information messages until further notice. If the NAK flag is set (bit 6 of the opcode byte), it indicates that 
        a selective retransmission of the frame identified by the Rx Sequence Number is being requested.
        """
        ack = event.packet

        if ack.rx_seq_num > circuit.hw:
            for seq in range(ack.rx_seq_num, circuit.hw + 1):
                seq = (seq + 127) % 128
                fut = circuit.info_futures.get(seq)
                if fut:
                    fut.set_result(True)
            circuit.hw = ack.rx_seq_num
        else:
            # Out of sync, error
            pass

        if ack.rx_seq_num == circuit.send_state():
            seq = (ack.rx_seq_num + 127) % 128
            fut = circuit.info_futures.get(seq)
            if fut:
                fut.set_result(True)
        elif ack.rx_seq_num < circuit.send_state():
            # Lagging behind
            pass
        else:
            # Invalid state, error
            pass

        if ack.choke():
            logger.warning("Got Info Choke")
            # TODO stop sending until further notice
            pass
        if ack.nak():
            seq_resend = event.packet.rx_seq_num
            logger.warning(f"Got Info NAK, rewinding to {seq_resend}")
            while seq_resend < circuit.send_state():
                info_to_resend = circuit.sent_info[seq_resend]
                info_to_resend.rx_seq_num = circuit.recv_state()
                netrom.write_packet(info_to_resend)
                seq_resend += 1
        elif event.packet.rx_seq_num != circuit.send_state():
            logger.warning("Info sync error")
            # Out of sync, what now? Update circuit send state?
            pass
        return NetRomStateType.Connected
    elif event.event_type == NetRomEventType.NL_CONNECT:
        conn = NetRomConnectRequest(
            circuit.remote_call,
            circuit.local_call,
            7,  # TODO configure TTL
            circuit.circuit_idx,
            circuit.circuit_id,
            0,  # Unused
            0,  # Unused
            OpType.ConnectRequest.as_op_byte(False, False, False),
            2,  # TODO get this from config
            circuit.origin_user,  # Origin user
            circuit.origin_node,  # Origin node
        )
        netrom.write_packet(conn)
        circuit.timer.reset()
        return NetRomStateType.AwaitingConnection
    elif event.event_type == NetRomEventType.NL_DISCONNECT:
        disc = NetRomPacket(
            circuit.remote_call,
            circuit.local_call,
            7,  # TODO configure TTL
            circuit.remote_circuit_idx,
            circuit.remote_circuit_id,
            0,
            0,
            OpType.DisconnectRequest.as_op_byte(False, False, False))
        netrom.write_packet(disc)
        circuit.timer.cancel()
        return NetRomStateType.AwaitingRelease
    elif event.event_type == NetRomEventType.NL_DATA:
        if event.mtu > 0:
            fragments = list(chunks(event.data, event.mtu))
            for fragment in fragments[:-1]:
                info = NetRomInfo(
                    circuit.remote_call,
                    circuit.local_call,
                    7,  # TODO
                    circuit.remote_circuit_idx,
                    circuit.remote_circuit_id,
                    circuit.send_state(),
                    circuit.recv_state(),
                    OpType.Information.as_op_byte(False, False, True),
                    fragment)
                netrom.write_packet(info)
                circuit.sent_info[info.tx_seq_num] = info
                circuit.info_futures[info.tx_seq_num] = event.future
                circuit.inc_send_state()
            last = fragments[-1]
        else:
            last = event.data
        info = NetRomInfo(
            circuit.remote_call,
            circuit.local_call,
            7,  # TODO
            circuit.remote_circuit_idx,
            circuit.remote_circuit_id,
            circuit.send_state(),
            circuit.recv_state(),
            OpType.Information.as_op_byte(False, False, False),
            last)
        netrom.write_packet(info)
        circuit.sent_info[info.tx_seq_num] = info
        circuit.info_futures[info.tx_seq_num] = event.future
        circuit.inc_send_state()

        async def check_ack():
            # TODO need to implement timeout and retry
            await asyncio.sleep(10.000)
            if circuit.hw <= info.tx_seq_num:
                # Retransmit
                print(f"Retransmit {info}")

        #asyncio.create_task(check_ack())

        return NetRomStateType.Connected
Exemplo n.º 29
0
 def __init__(self, environ):
     NetworkApp.__init__(self, environ)
     LoggingMixin.__init__(self)
     self.context = None
     self.info(f"Created with {environ}")
Exemplo n.º 30
0
def disconnected_handler(circuit: NetRomCircuit, event: NetRomStateEvent,
                         netrom: NetRom,
                         logger: LoggingMixin) -> NetRomStateType:
    assert circuit.state == NetRomStateType.Disconnected

    if event.event_type == NetRomEventType.NETROM_CONNECT:
        connect_req = cast(NetRomConnectRequest, event.packet)
        connect_ack = NetRomConnectAck(
            connect_req.source,
            connect_req.dest,
            7,  # TODO get TTL from config
            connect_req.circuit_idx,
            connect_req.circuit_id,
            circuit.circuit_idx,
            circuit.circuit_id,
            OpType.ConnectAcknowledge.as_op_byte(False, False, False),
            connect_req.proposed_window_size)
        circuit.remote_circuit_id = connect_req.circuit_id
        circuit.remote_circuit_idx = connect_req.circuit_idx
        if netrom.write_packet(connect_ack):
            netrom.nl_connect_indication(
                circuit.circuit_idx, circuit.circuit_id, circuit.remote_call,
                circuit.local_call, circuit.origin_node, circuit.origin_user)
            circuit.timer.reset()
            return NetRomStateType.Connected
        else:
            return NetRomStateType.Disconnected
    elif event.event_type in (NetRomEventType.NETROM_CONNECT_ACK,
                              NetRomEventType.NETROM_INFO,
                              NetRomEventType.NETROM_INFO_ACK):
        # If we're disconnected, we don't have the remote circuit's ID/IDX, so we can't really do
        # much here besides try to re-connect
        logger.debug(
            f"Got unexpected packet {event.packet}. Attempting to reconnect")
        disc = NetRomPacket(
            circuit.remote_call,
            circuit.local_call,
            7,  # TODO configure TTL
            circuit.remote_circuit_idx,
            circuit.remote_circuit_id,
            0,
            0,
            OpType.DisconnectRequest.as_op_byte(False, False, False))
        if netrom.write_packet(disc):
            return NetRomStateType.AwaitingRelease
        else:
            return NetRomStateType.Disconnected
    elif event.event_type == NetRomEventType.NETROM_DISCONNECT_ACK:
        # We are already disconnected, nothing to do here
        return NetRomStateType.Disconnected
    elif event.event_type == NetRomEventType.NETROM_DISCONNECT:
        # Ack this even though we're not connected
        disc_ack = NetRomPacket(
            event.packet.source,
            event.packet.dest,
            7,  # TODO configure TTL
            0,  # Don't know the remote circuit idx
            0,  # Don't know the remote circuit id
            0,  # Our circuit idx
            0,  # Our circuit id
            OpType.DisconnectAcknowledge.as_op_byte(False, False, False))
        netrom.write_packet(disc_ack)
        netrom.nl_disconnect_indication(circuit.circuit_idx,
                                        circuit.circuit_id,
                                        circuit.remote_call,
                                        circuit.local_call)
        circuit.timer.cancel()
        return NetRomStateType.Disconnected
    elif event.event_type == NetRomEventType.NL_CONNECT:
        conn = NetRomConnectRequest(
            circuit.remote_call,
            circuit.local_call,
            7,  # TODO configure TTL
            circuit.circuit_idx,
            circuit.circuit_id,
            0,  # Send no circuit idx
            0,  # Send no circuit id
            OpType.ConnectRequest.as_op_byte(False, False, False),
            2,  # Proposed window size (TODO get this from config)
            circuit.origin_user,  # Origin user
            circuit.origin_node,  # Origin node
        )
        if netrom.write_packet(conn):
            circuit.timer.start()
            return NetRomStateType.AwaitingConnection
        else:
            return NetRomStateType.Disconnected
    elif event.event_type == NetRomEventType.NL_DISCONNECT:
        return NetRomStateType.Disconnected
    elif event.event_type == NetRomEventType.NL_DATA:
        logger.debug("Ignoring unexpected NL_DATA event in disconnected state")
        return NetRomStateType.Disconnected