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 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 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 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