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)
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
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)
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()
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)
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)
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()
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)
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
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
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)
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
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)
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
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))
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()
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()
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)
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")
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)
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)
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
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)
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")
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"))
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)
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}")
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
def __init__(self, environ): NetworkApp.__init__(self, environ) LoggingMixin.__init__(self) self.context = None self.info(f"Created with {environ}")
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