"""Tests for I2NP message types.""" import hashlib import os import struct import pytest class TestI2NPHeader: def test_construct(self): from i2p_data.i2np import I2NPHeader h = I2NPHeader(msg_type=10, msg_id=12345, expiration=1710000000000, size=100, checksum=0xAB) assert h.msg_type == 10 assert h.msg_id == 12345 assert h.expiration == 1710000000000 assert h.size == 100 assert h.checksum == 0xAB def test_header_size_is_16(self): from i2p_data.i2np import I2NPHeader h = I2NPHeader(msg_type=1, msg_id=0, expiration=0, size=0, checksum=0) assert len(h.to_bytes()) == 16 def test_roundtrip(self): from i2p_data.i2np import I2NPHeader h = I2NPHeader(msg_type=20, msg_id=0xDEADBEEF, expiration=9999999999999, size=512, checksum=0xFF) data = h.to_bytes() h2 = I2NPHeader.from_bytes(data) assert h2.msg_type == h.msg_type assert h2.msg_id == h.msg_id assert h2.expiration == h.expiration assert h2.size == h.size assert h2.checksum == h.checksum def test_from_stream(self): import io from i2p_data.i2np import I2NPHeader h = I2NPHeader(msg_type=2, msg_id=42, expiration=1000, size=10, checksum=5) stream = io.BytesIO(h.to_bytes() + b"extra data") h2 = I2NPHeader.from_stream(stream) assert h2.msg_type == 2 assert h2.msg_id == 42 # Stream should have advanced past header assert stream.read() == b"extra data" def test_from_bytes_truncated(self): from i2p_data.i2np import I2NPHeader with pytest.raises(ValueError): I2NPHeader.from_bytes(b"\x00" * 10) class TestChecksum: def test_calculate_checksum(self): from i2p_data.i2np import I2NPMessage body = b"hello world" expected = hashlib.sha256(body).digest()[0] assert I2NPMessage.calculate_checksum(body) == expected def test_checksum_empty_body(self): from i2p_data.i2np import I2NPMessage expected = hashlib.sha256(b"").digest()[0] assert I2NPMessage.calculate_checksum(b"") == expected class TestTypeRegistry: def test_delivery_status_registered(self): from i2p_data.i2np import I2NPMessage, DeliveryStatusMessage assert I2NPMessage._registry[10] is DeliveryStatusMessage def test_data_message_registered(self): from i2p_data.i2np import I2NPMessage, DataMessage assert I2NPMessage._registry[20] is DataMessage def test_database_store_registered(self): from i2p_data.i2np import I2NPMessage, DatabaseStoreMessage assert I2NPMessage._registry[1] is DatabaseStoreMessage def test_database_lookup_registered(self): from i2p_data.i2np import I2NPMessage, DatabaseLookupMessage assert I2NPMessage._registry[2] is DatabaseLookupMessage def test_unknown_type(self): from i2p_data.i2np import I2NPMessage, I2NPHeader # Build a message with unknown type 255 body = b"\x00" * 4 checksum = I2NPMessage.calculate_checksum(body) header = I2NPHeader(msg_type=255, msg_id=1, expiration=0, size=len(body), checksum=checksum) data = header.to_bytes() + body with pytest.raises(ValueError, match="Unknown"): I2NPMessage.from_bytes(data) class TestDeliveryStatusMessage: def test_construct(self): from i2p_data.i2np import DeliveryStatusMessage msg = DeliveryStatusMessage(msg_id=1234, arrival_time=1710000000000) assert msg.msg_id == 1234 assert msg.arrival_time == 1710000000000 assert msg.TYPE == 10 def test_body_size(self): from i2p_data.i2np import DeliveryStatusMessage msg = DeliveryStatusMessage(msg_id=1, arrival_time=0) assert len(msg.body_bytes()) == 12 # 4 + 8 def test_roundtrip(self): from i2p_data.i2np import DeliveryStatusMessage, I2NPMessage msg = DeliveryStatusMessage(msg_id=0xCAFE, arrival_time=1710000000000) data = msg.to_bytes() msg2 = I2NPMessage.from_bytes(data) assert isinstance(msg2, DeliveryStatusMessage) assert msg2.msg_id == 0xCAFE assert msg2.arrival_time == 1710000000000 def test_checksum_validated(self): from i2p_data.i2np import DeliveryStatusMessage, I2NPMessage msg = DeliveryStatusMessage(msg_id=1, arrival_time=100) data = bytearray(msg.to_bytes()) # Corrupt the checksum byte (byte 15 in header) data[15] ^= 0xFF with pytest.raises(ValueError, match="[Cc]hecksum"): I2NPMessage.from_bytes(bytes(data)) class TestDataMessage: def test_construct(self): from i2p_data.i2np import DataMessage payload = b"hello i2p" msg = DataMessage(payload=payload) assert msg.payload == payload assert msg.TYPE == 20 def test_body_format(self): from i2p_data.i2np import DataMessage payload = b"\x01\x02\x03" msg = DataMessage(payload=payload) body = msg.body_bytes() length = struct.unpack("!I", body[:4])[0] assert length == 3 assert body[4:] == payload def test_roundtrip(self): from i2p_data.i2np import DataMessage, I2NPMessage payload = os.urandom(128) msg = DataMessage(payload=payload) data = msg.to_bytes() msg2 = I2NPMessage.from_bytes(data) assert isinstance(msg2, DataMessage) assert msg2.payload == payload def test_empty_payload(self): from i2p_data.i2np import DataMessage, I2NPMessage msg = DataMessage(payload=b"") data = msg.to_bytes() msg2 = I2NPMessage.from_bytes(data) assert isinstance(msg2, DataMessage) assert msg2.payload == b"" class TestDatabaseStoreMessage: def test_construct(self): from i2p_data.i2np import DatabaseStoreMessage key = os.urandom(32) msg = DatabaseStoreMessage(key=key, ds_type=1, reply_token=42, data=b"some data") assert msg.key == key assert msg.ds_type == 1 assert msg.reply_token == 42 assert msg.data == b"some data" assert msg.TYPE == 1 def test_roundtrip(self): from i2p_data.i2np import DatabaseStoreMessage, I2NPMessage key = os.urandom(32) payload = os.urandom(64) msg = DatabaseStoreMessage(key=key, ds_type=0, reply_token=9999, data=payload) data = msg.to_bytes() msg2 = I2NPMessage.from_bytes(data) assert isinstance(msg2, DatabaseStoreMessage) assert msg2.key == key assert msg2.ds_type == 0 assert msg2.reply_token == 9999 assert msg2.data == payload def test_body_structure(self): from i2p_data.i2np import DatabaseStoreMessage key = os.urandom(32) msg = DatabaseStoreMessage(key=key, ds_type=3, reply_token=0, data=b"xyz") body = msg.body_bytes() assert body[:32] == key assert body[32] == 3 # type byte assert struct.unpack("!I", body[33:37])[0] == 0 # reply_token assert body[37:] == b"xyz" class TestDatabaseLookupMessage: def test_construct_no_tunnel(self): from i2p_data.i2np import DatabaseLookupMessage key = os.urandom(32) from_hash = os.urandom(32) msg = DatabaseLookupMessage(key=key, from_hash=from_hash, flags=0, reply_tunnel_id=0, exclude_list=[]) assert msg.key == key assert msg.from_hash == from_hash assert msg.flags == 0 assert msg.TYPE == 2 def test_roundtrip_no_excludes(self): from i2p_data.i2np import DatabaseLookupMessage, I2NPMessage key = os.urandom(32) from_hash = os.urandom(32) msg = DatabaseLookupMessage(key=key, from_hash=from_hash, flags=0, reply_tunnel_id=0, exclude_list=[]) data = msg.to_bytes() msg2 = I2NPMessage.from_bytes(data) assert isinstance(msg2, DatabaseLookupMessage) assert msg2.key == key assert msg2.from_hash == from_hash assert msg2.flags == 0 assert msg2.exclude_list == [] def test_roundtrip_with_tunnel_and_excludes(self): from i2p_data.i2np import DatabaseLookupMessage, I2NPMessage key = os.urandom(32) from_hash = os.urandom(32) excludes = [os.urandom(32) for _ in range(3)] # Flag bit 0 set means reply to tunnel msg = DatabaseLookupMessage(key=key, from_hash=from_hash, flags=0x01, reply_tunnel_id=5555, exclude_list=excludes) data = msg.to_bytes() msg2 = I2NPMessage.from_bytes(data) assert isinstance(msg2, DatabaseLookupMessage) assert msg2.key == key assert msg2.from_hash == from_hash assert msg2.flags == 0x01 assert msg2.reply_tunnel_id == 5555 assert len(msg2.exclude_list) == 3 for i in range(3): assert msg2.exclude_list[i] == excludes[i] def test_body_structure_with_tunnel(self): from i2p_data.i2np import DatabaseLookupMessage key = os.urandom(32) from_hash = os.urandom(32) msg = DatabaseLookupMessage(key=key, from_hash=from_hash, flags=0x01, reply_tunnel_id=100, exclude_list=[]) body = msg.body_bytes() assert body[:32] == key assert body[32:64] == from_hash assert body[64] == 0x01 # flags # When flag bit 0 set, next 4 bytes are tunnel ID assert struct.unpack("!I", body[65:69])[0] == 100 assert body[69] == 0 # exclude count class TestI2NPMessageBase: def test_from_bytes_truncated_header(self): from i2p_data.i2np import I2NPMessage with pytest.raises(ValueError): I2NPMessage.from_bytes(b"\x00" * 5) def test_from_bytes_truncated_body(self): from i2p_data.i2np import I2NPMessage, I2NPHeader # Header says size=100 but body is only 2 bytes body = b"\x00\x00" checksum = I2NPMessage.calculate_checksum(body) header = I2NPHeader(msg_type=10, msg_id=1, expiration=0, size=100, checksum=checksum) data = header.to_bytes() + body with pytest.raises(ValueError): I2NPMessage.from_bytes(data)