A Python port of the Invisible Internet Project (I2P)
at main 80 lines 2.2 kB view raw
1"""NTCP2 transport protocol types.""" 2 3import enum 4import struct 5 6 7class FrameType(enum.IntEnum): 8 DATA = 0 9 PADDING = 254 10 DATETIME = 1 11 OPTIONS = 2 12 ROUTER_INFO = 3 13 I2NP = 4 14 TERMINATION = 5 15 16 17class TransportState(enum.Enum): 18 UNKNOWN = 0 19 HANDSHAKING = 1 20 ESTABLISHED = 2 21 TERMINATED = 3 22 ERROR = 4 23 24 25class NTCP2Frame: 26 """NTCP2 block/frame: type(1) + length(2) + payload.""" 27 28 def __init__(self, frame_type: FrameType, payload: bytes): 29 self.frame_type = frame_type 30 self.payload = payload 31 32 def to_bytes(self) -> bytes: 33 return struct.pack("!BH", self.frame_type, len(self.payload)) + self.payload 34 35 @classmethod 36 def from_bytes(cls, data: bytes) -> "NTCP2Frame": 37 frame_type, length = struct.unpack("!BH", data[:3]) 38 return cls(FrameType(frame_type), data[3:3 + length]) 39 40 @classmethod 41 def from_stream(cls, stream) -> "NTCP2Frame": 42 header = stream.read(3) 43 frame_type, length = struct.unpack("!BH", header) 44 payload = stream.read(length) 45 return cls(FrameType(frame_type), payload) 46 47 48class NTCP2SessionState: 49 """NTCP2 session state machine.""" 50 51 def __init__(self): 52 self.state = TransportState.UNKNOWN 53 self.error_message: str | None = None 54 self.messages_sent = 0 55 self.messages_received = 0 56 self._termination_reason: int | None = None 57 58 def begin_handshake(self): 59 if self.state != TransportState.UNKNOWN: 60 raise RuntimeError(f"Cannot begin handshake from {self.state}") 61 self.state = TransportState.HANDSHAKING 62 63 def complete_handshake(self): 64 if self.state != TransportState.HANDSHAKING: 65 raise RuntimeError(f"Cannot complete handshake from {self.state}") 66 self.state = TransportState.ESTABLISHED 67 68 def terminate(self, reason: int = 0): 69 self._termination_reason = reason 70 self.state = TransportState.TERMINATED 71 72 def set_error(self, message: str): 73 self.error_message = message 74 self.state = TransportState.ERROR 75 76 def on_message_sent(self): 77 self.messages_sent += 1 78 79 def on_message_received(self): 80 self.messages_received += 1