"""Tests for I2CP message types.""" import os import struct import pytest class TestI2CPMessage: def test_wire_format_length_prefix(self): from i2p_client.i2cp_messages import GetDateMessage, I2CPMessage msg = GetDateMessage(version="0.9.62") wire = msg.to_wire() # Wire format: 4-byte length + 1-byte type + payload length = struct.unpack("!I", wire[:4])[0] assert length == len(wire) - 4 # length does not include itself def test_from_wire_dispatches(self): from i2p_client.i2cp_messages import GetDateMessage, I2CPMessage msg = GetDateMessage(version="0.9.62") wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, GetDateMessage) assert msg2.version == "0.9.62" def test_unknown_type(self): from i2p_client.i2cp_messages import I2CPMessage # Build wire with unknown type 254 payload = b"test" wire = struct.pack("!IB", len(payload) + 1, 254) + payload with pytest.raises(ValueError, match="Unknown"): I2CPMessage.from_wire(wire) def test_from_wire_truncated(self): from i2p_client.i2cp_messages import I2CPMessage with pytest.raises(ValueError): I2CPMessage.from_wire(b"\x00\x00") class TestGetDateMessage: def test_construct(self): from i2p_client.i2cp_messages import GetDateMessage msg = GetDateMessage(version="0.9.62") assert msg.TYPE == 32 assert msg.version == "0.9.62" def test_roundtrip(self): from i2p_client.i2cp_messages import GetDateMessage, I2CPMessage msg = GetDateMessage(version="0.9.58") wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, GetDateMessage) assert msg2.version == "0.9.58" class TestSetDateMessage: def test_construct(self): from i2p_client.i2cp_messages import SetDateMessage msg = SetDateMessage(date_ms=1710000000000, version="0.9.62") assert msg.TYPE == 33 assert msg.date_ms == 1710000000000 def test_roundtrip(self): from i2p_client.i2cp_messages import SetDateMessage, I2CPMessage msg = SetDateMessage(date_ms=9999999999999, version="0.9.62") wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, SetDateMessage) assert msg2.date_ms == 9999999999999 assert msg2.version == "0.9.62" class TestSessionStatusMessage: def test_construct(self): from i2p_client.i2cp_messages import SessionStatusMessage msg = SessionStatusMessage(session_id=1, status=0) assert msg.TYPE == 20 assert msg.session_id == 1 assert msg.status == 0 def test_status_codes(self): from i2p_client.i2cp_messages import SessionStatusMessage # CREATED=0, UPDATED=1, INVALID=2, REFUSED=3 for code in [0, 1, 2, 3]: msg = SessionStatusMessage(session_id=0, status=code) assert msg.status == code def test_roundtrip(self): from i2p_client.i2cp_messages import SessionStatusMessage, I2CPMessage msg = SessionStatusMessage(session_id=42, status=1) wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, SessionStatusMessage) assert msg2.session_id == 42 assert msg2.status == 1 class TestCreateSessionMessage: def test_roundtrip(self): from i2p_client.i2cp_messages import CreateSessionMessage, I2CPMessage dest_data = os.urandom(387) options = {"inbound.quantity": "3", "outbound.length": "2"} msg = CreateSessionMessage(destination_data=dest_data, options=options) assert msg.TYPE == 1 wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, CreateSessionMessage) assert msg2.destination_data == dest_data assert msg2.options["inbound.quantity"] == "3" class TestDestroySessionMessage: def test_roundtrip(self): from i2p_client.i2cp_messages import DestroySessionMessage, I2CPMessage msg = DestroySessionMessage(session_id=5) assert msg.TYPE == 3 wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, DestroySessionMessage) assert msg2.session_id == 5 class TestSendMessageMessage: def test_roundtrip(self): from i2p_client.i2cp_messages import SendMessageMessage, I2CPMessage dest = os.urandom(384) + b'\x00\x00\x00' # 384 key bytes + null cert payload = os.urandom(128) msg = SendMessageMessage(session_id=7, destination_data=dest, payload=payload) assert msg.TYPE == 5 wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, SendMessageMessage) assert msg2.session_id == 7 assert msg2.destination_data == dest assert msg2.payload == payload class TestMessageStatusMessage: def test_roundtrip(self): from i2p_client.i2cp_messages import MessageStatusMessage, I2CPMessage msg = MessageStatusMessage(session_id=3, msg_id=9999, nonce=42, status=1, size=100) assert msg.TYPE == 22 wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, MessageStatusMessage) assert msg2.session_id == 3 assert msg2.msg_id == 9999 assert msg2.nonce == 42 assert msg2.status == 1 assert msg2.size == 100 class TestMessagePayloadMessage: def test_roundtrip(self): from i2p_client.i2cp_messages import MessagePayloadMessage, I2CPMessage payload = os.urandom(256) msg = MessagePayloadMessage(session_id=1, msg_id=555, payload=payload) assert msg.TYPE == 31 wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, MessagePayloadMessage) assert msg2.session_id == 1 assert msg2.msg_id == 555 assert msg2.payload == payload class TestReceiveMessageBeginMessage: def test_roundtrip(self): from i2p_client.i2cp_messages import ReceiveMessageBeginMessage, I2CPMessage msg = ReceiveMessageBeginMessage(session_id=2, msg_id=100) assert msg.TYPE == 6 wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, ReceiveMessageBeginMessage) assert msg2.session_id == 2 assert msg2.msg_id == 100 class TestReceiveMessageEndMessage: def test_roundtrip(self): from i2p_client.i2cp_messages import ReceiveMessageEndMessage, I2CPMessage msg = ReceiveMessageEndMessage(session_id=4, msg_id=200) assert msg.TYPE == 7 wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, ReceiveMessageEndMessage) assert msg2.session_id == 4 assert msg2.msg_id == 200 class TestHostLookupMessage: def test_roundtrip_by_name(self): from i2p_client.i2cp_messages import HostLookupMessage, I2CPMessage msg = HostLookupMessage(session_id=1, request_id=42, hostname="example.i2p") assert msg.TYPE == 38 wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, HostLookupMessage) assert msg2.session_id == 1 assert msg2.request_id == 42 assert msg2.hostname == "example.i2p" def test_roundtrip_by_hash(self): from i2p_client.i2cp_messages import HostLookupMessage, I2CPMessage h = os.urandom(32) msg = HostLookupMessage(session_id=1, request_id=10, dest_hash=h) wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, HostLookupMessage) assert msg2.dest_hash == h assert msg2.hostname is None class TestHostReplyMessage: def test_roundtrip_success(self): from i2p_client.i2cp_messages import HostReplyMessage, I2CPMessage dest_data = os.urandom(387) msg = HostReplyMessage(session_id=1, request_id=42, result_code=0, destination_data=dest_data) assert msg.TYPE == 39 wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, HostReplyMessage) assert msg2.result_code == 0 assert msg2.destination_data == dest_data def test_roundtrip_failure(self): from i2p_client.i2cp_messages import HostReplyMessage, I2CPMessage msg = HostReplyMessage(session_id=1, request_id=10, result_code=1) wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, HostReplyMessage) assert msg2.result_code == 1 assert msg2.destination_data is None # --- Tier 0 constant tests --- def test_session_status_constants(): from i2p_client.i2cp_messages import SessionStatusMessage assert SessionStatusMessage.STATUS_DESTROYED == 0 assert SessionStatusMessage.STATUS_CREATED == 1 assert SessionStatusMessage.STATUS_UPDATED == 2 assert SessionStatusMessage.STATUS_INVALID == 3 assert SessionStatusMessage.STATUS_REFUSED == 4 def test_message_status_constants(): from i2p_client.i2cp_messages import MessageStatusMessage assert MessageStatusMessage.STATUS_AVAILABLE == 0 assert MessageStatusMessage.STATUS_SEND_ACCEPTED == 1 assert MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS == 2 assert MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE == 3 assert MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS == 4 assert MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE == 5 assert MessageStatusMessage.STATUS_SEND_SUCCESS_LOCAL == 6 assert MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL == 7 assert MessageStatusMessage.STATUS_SEND_FAILURE_ROUTER == 8 assert MessageStatusMessage.STATUS_SEND_FAILURE_NETWORK == 9 assert MessageStatusMessage.STATUS_SEND_FAILURE_BAD_SESSION == 10 assert MessageStatusMessage.STATUS_SEND_FAILURE_BAD_MESSAGE == 11 assert MessageStatusMessage.STATUS_SEND_FAILURE_BAD_OPTIONS == 12 assert MessageStatusMessage.STATUS_SEND_FAILURE_OVERFLOW == 13 assert MessageStatusMessage.STATUS_SEND_FAILURE_EXPIRED == 14 assert MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL_LEASESET == 15 assert MessageStatusMessage.STATUS_SEND_FAILURE_NO_TUNNELS == 16 assert MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION == 17 assert MessageStatusMessage.STATUS_SEND_FAILURE_DESTINATION == 18 assert MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET == 19 assert MessageStatusMessage.STATUS_SEND_FAILURE_EXPIRED_LEASESET == 20 assert MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET == 21 def test_message_status_is_success(): from i2p_client.i2cp_messages import MessageStatusMessage for status in (1, 2, 4, 6): msg = MessageStatusMessage(0, 0, 0, status, 0) assert msg.is_success() for status in (0, 3, 5, 7, 8, 9, 10, 21): msg = MessageStatusMessage(0, 0, 0, status, 0) assert not msg.is_success() def test_message_status_is_failure(): from i2p_client.i2cp_messages import MessageStatusMessage for status in (3, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21): msg = MessageStatusMessage(0, 0, 0, status, 0) assert msg.is_failure() for status in (0, 1, 2, 4, 6): msg = MessageStatusMessage(0, 0, 0, status, 0) assert not msg.is_failure() def test_message_status_name(): from i2p_client.i2cp_messages import MessageStatusMessage msg = MessageStatusMessage(0, 0, 0, 0, 0) assert msg.status_name() == "AVAILABLE" msg2 = MessageStatusMessage(0, 0, 0, 4, 0) assert msg2.status_name() == "SEND_GUARANTEED_SUCCESS" def test_host_reply_result_constants(): from i2p_client.i2cp_messages import HostReplyMessage assert HostReplyMessage.RESULT_SUCCESS == 0 assert HostReplyMessage.RESULT_FAILURE == 1 assert HostReplyMessage.RESULT_SECRET_REQUIRED == 2 assert HostReplyMessage.RESULT_KEY_REQUIRED == 3 assert HostReplyMessage.RESULT_SECRET_AND_KEY_REQUIRED == 4 assert HostReplyMessage.RESULT_DECRYPTION_FAILURE == 5 def test_host_lookup_type_constants(): from i2p_client.i2cp_messages import HostLookupMessage assert HostLookupMessage.LOOKUP_HASH == 0 assert HostLookupMessage.LOOKUP_HOST == 1 def test_get_date_with_options_roundtrip(): from i2p_client.i2cp_messages import GetDateMessage, I2CPMessage msg = GetDateMessage(version="0.9.62", options={"i2cp.username": "alice", "i2cp.password": "secret"}) wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, GetDateMessage) assert msg2.version == "0.9.62" assert msg2.options == {"i2cp.username": "alice", "i2cp.password": "secret"} def test_get_date_no_options_backward_compat(): from i2p_client.i2cp_messages import GetDateMessage, I2CPMessage msg = GetDateMessage(version="0.9.62") wire = msg.to_wire() msg2 = I2CPMessage.from_wire(wire) assert isinstance(msg2, GetDateMessage) assert msg2.version == "0.9.62" assert msg2.options == {}