"""I2NP tunnel message types — TunnelData, TunnelGateway, TunnelBuild variants.""" import struct from i2p_data.i2np import I2NPMessage class TunnelDataMessage(I2NPMessage): """Type 18: tunnel_id(4) + data(1024 bytes).""" TYPE = 18 RECORD_SIZE = 1024 def __init__(self, tunnel_id: int, data: bytes): self.tunnel_id = tunnel_id self.data = data self._header_msg_id = 0 self._header_expiration = 0 def body_bytes(self) -> bytes: return struct.pack("!I", self.tunnel_id) + self.data @classmethod def _from_body(cls, body: bytes) -> "TunnelDataMessage": tunnel_id = struct.unpack("!I", body[:4])[0] return cls(tunnel_id, body[4:]) class TunnelGatewayMessage(I2NPMessage): """Type 19: tunnel_id(4) + length(2) + data.""" TYPE = 19 def __init__(self, tunnel_id: int, data: bytes): self.tunnel_id = tunnel_id self.data = data self._header_msg_id = 0 self._header_expiration = 0 def body_bytes(self) -> bytes: return struct.pack("!IH", self.tunnel_id, len(self.data)) + self.data @classmethod def _from_body(cls, body: bytes) -> "TunnelGatewayMessage": tunnel_id, length = struct.unpack("!IH", body[:6]) return cls(tunnel_id, body[6:6 + length]) class TunnelBuildMessage(I2NPMessage): """Type 21: 8 records of 528 bytes each.""" TYPE = 21 NUM_RECORDS = 8 RECORD_SIZE = 528 def __init__(self, records: list[bytes]): if len(records) != self.NUM_RECORDS: raise ValueError(f"TunnelBuildMessage requires exactly {self.NUM_RECORDS} records, got {len(records)}") self.records = records self._header_msg_id = 0 self._header_expiration = 0 def body_bytes(self) -> bytes: return b"".join(self.records) @classmethod def _from_body(cls, body: bytes) -> "TunnelBuildMessage": records = [body[i * cls.RECORD_SIZE:(i + 1) * cls.RECORD_SIZE] for i in range(cls.NUM_RECORDS)] return cls(records) class TunnelBuildReplyMessage(I2NPMessage): """Type 22: 8 records of 528 bytes each.""" TYPE = 22 NUM_RECORDS = 8 RECORD_SIZE = 528 def __init__(self, records: list[bytes]): if len(records) != self.NUM_RECORDS: raise ValueError(f"TunnelBuildReplyMessage requires exactly {self.NUM_RECORDS} records") self.records = records self._header_msg_id = 0 self._header_expiration = 0 def body_bytes(self) -> bytes: return b"".join(self.records) @classmethod def _from_body(cls, body: bytes) -> "TunnelBuildReplyMessage": records = [body[i * cls.RECORD_SIZE:(i + 1) * cls.RECORD_SIZE] for i in range(cls.NUM_RECORDS)] return cls(records) class VariableTunnelBuildMessage(I2NPMessage): """Type 23: count(1) + N records of 528 bytes.""" TYPE = 23 RECORD_SIZE = 528 def __init__(self, records: list[bytes]): self.records = records self._header_msg_id = 0 self._header_expiration = 0 def body_bytes(self) -> bytes: return struct.pack("!B", len(self.records)) + b"".join(self.records) @classmethod def _from_body(cls, body: bytes) -> "VariableTunnelBuildMessage": count = body[0] records = [body[1 + i * cls.RECORD_SIZE:1 + (i + 1) * cls.RECORD_SIZE] for i in range(count)] return cls(records) class VariableTunnelBuildReplyMessage(I2NPMessage): """Type 24: count(1) + N records of 528 bytes.""" TYPE = 24 RECORD_SIZE = 528 def __init__(self, records: list[bytes]): self.records = records self._header_msg_id = 0 self._header_expiration = 0 def body_bytes(self) -> bytes: return struct.pack("!B", len(self.records)) + b"".join(self.records) @classmethod def _from_body(cls, body: bytes) -> "VariableTunnelBuildReplyMessage": count = body[0] records = [body[1 + i * cls.RECORD_SIZE:1 + (i + 1) * cls.RECORD_SIZE] for i in range(count)] return cls(records)