"""Tests for i2p_data.certificate — Certificate type system.""" import io import struct import pytest from i2p_data.certificate import ( Certificate, CertificateType, KeyCertificate, ) class TestCertificateType: """CertificateType enum values match the I2P spec.""" def test_null_is_zero(self): assert CertificateType.NULL == 0 def test_hashcash_is_one(self): assert CertificateType.HASHCASH == 1 def test_hidden_is_two(self): assert CertificateType.HIDDEN == 2 def test_signed_is_three(self): assert CertificateType.SIGNED == 3 def test_multiple_is_four(self): assert CertificateType.MULTIPLE == 4 def test_key_is_five(self): assert CertificateType.KEY == 5 class TestCertificateNull: """NULL certificate serialization.""" def test_null_to_bytes(self): assert Certificate.NULL.to_bytes() == b"\x00\x00\x00" def test_null_from_bytes_roundtrip(self): cert = Certificate.from_bytes(b"\x00\x00\x00") assert cert.cert_type == CertificateType.NULL assert cert.payload == b"" assert cert.to_bytes() == b"\x00\x00\x00" def test_null_len(self): assert len(Certificate.NULL) == 3 class TestCertificateWithPayload: """Certificate with a payload roundtrips correctly.""" def test_hashcash_roundtrip(self): payload = b"hashcash-stamp-data" cert = Certificate(CertificateType.HASHCASH, payload) serialized = cert.to_bytes() restored = Certificate.from_bytes(serialized) assert restored.cert_type == CertificateType.HASHCASH assert restored.payload == payload def test_serialized_length(self): payload = b"abcdef" cert = Certificate(CertificateType.HASHCASH, payload) assert len(cert) == 3 + len(payload) assert len(cert.to_bytes()) == len(cert) class TestCertificateFromStream: """from_stream reads correctly and positions the stream after the cert.""" def test_stream_position_after_read(self): cert_bytes = b"\x00\x00\x00" trailing = b"\xDE\xAD" stream = io.BytesIO(cert_bytes + trailing) cert = Certificate.from_stream(stream) assert cert.cert_type == CertificateType.NULL assert stream.read() == trailing def test_stream_with_payload(self): payload = b"\x01\x02\x03\x04" data = struct.pack("!BH", CertificateType.HASHCASH, len(payload)) + payload trailing = b"\xFF" stream = io.BytesIO(data + trailing) cert = Certificate.from_stream(stream) assert cert.cert_type == CertificateType.HASHCASH assert cert.payload == payload assert stream.read() == trailing def test_stream_short_header_raises(self): with pytest.raises(ValueError, match="3 bytes"): Certificate.from_stream(io.BytesIO(b"\x00\x00")) def test_stream_short_payload_raises(self): # Header says 5 bytes payload but only 2 available data = struct.pack("!BH", CertificateType.HASHCASH, 5) + b"\x01\x02" with pytest.raises(ValueError, match="payload bytes"): Certificate.from_stream(io.BytesIO(data)) class TestKeyCertificate: """KeyCertificate structured access.""" def _make_key_cert_payload(self, sig_code: int, enc_code: int, extra: bytes = b"") -> bytes: return struct.pack("!HH", sig_code, enc_code) + extra def test_sig_and_enc_type_codes(self): # EdDSA_SHA512_Ed25519 = code 7, ECIES_X25519 = code 4 payload = self._make_key_cert_payload(7, 4) kc = KeyCertificate(payload) assert kc.get_sig_type_code() == 7 assert kc.get_enc_type_code() == 4 def test_get_sig_type_returns_enum(self): payload = self._make_key_cert_payload(7, 4) kc = KeyCertificate(payload) from i2p_crypto.dsa import SigType assert kc.get_sig_type() == SigType.EdDSA_SHA512_Ed25519 def test_extra_key_data(self): extra = b"\xAA\xBB\xCC" payload = self._make_key_cert_payload(7, 4, extra) kc = KeyCertificate(payload) assert kc.get_extra_key_data() == extra def test_no_extra_key_data(self): payload = self._make_key_cert_payload(1, 0) kc = KeyCertificate(payload) assert kc.get_extra_key_data() == b"" def test_payload_too_short_raises(self): with pytest.raises(ValueError, match=">= 4 bytes"): KeyCertificate(b"\x00\x01") def test_payload_exactly_four_bytes(self): payload = self._make_key_cert_payload(0, 0) kc = KeyCertificate(payload) assert kc.get_sig_type_code() == 0 assert kc.get_enc_type_code() == 0 def test_cert_type_is_key(self): payload = self._make_key_cert_payload(7, 4) kc = KeyCertificate(payload) assert kc.cert_type == CertificateType.KEY class TestKeyCertificateAutoDetection: """Certificate.from_bytes auto-creates KeyCertificate for type KEY.""" def test_from_bytes_returns_key_certificate(self): payload = struct.pack("!HH", 7, 4) data = struct.pack("!BH", CertificateType.KEY, len(payload)) + payload cert = Certificate.from_bytes(data) assert isinstance(cert, KeyCertificate) assert cert.get_sig_type_code() == 7 def test_from_bytes_non_key_returns_certificate(self): data = struct.pack("!BH", CertificateType.HASHCASH, 0) cert = Certificate.from_bytes(data) assert type(cert) is Certificate assert not isinstance(cert, KeyCertificate) class TestCertificateEqualityAndHash: """Equality and hashing.""" def test_equal_certificates(self): a = Certificate(CertificateType.HASHCASH, b"\x01\x02") b = Certificate(CertificateType.HASHCASH, b"\x01\x02") assert a == b def test_unequal_type(self): a = Certificate(CertificateType.HASHCASH, b"") b = Certificate(CertificateType.HIDDEN, b"") assert a != b def test_unequal_payload(self): a = Certificate(CertificateType.HASHCASH, b"\x01") b = Certificate(CertificateType.HASHCASH, b"\x02") assert a != b def test_hash_equal_for_equal_certs(self): a = Certificate(CertificateType.NULL) b = Certificate(CertificateType.NULL) assert hash(a) == hash(b) def test_usable_in_set(self): a = Certificate(CertificateType.NULL) b = Certificate(CertificateType.NULL) c = Certificate(CertificateType.HASHCASH, b"\x01") s = {a, b, c} assert len(s) == 2 def test_not_equal_to_non_certificate(self): assert Certificate(CertificateType.NULL) != "not a cert" class TestKeyCertificateRoundtrip: """KeyCertificate serialization roundtrip.""" def test_roundtrip_with_extra_data(self): extra = b"\x01\x02\x03\x04\x05" payload = struct.pack("!HH", 7, 4) + extra kc = KeyCertificate(payload) serialized = kc.to_bytes() restored = Certificate.from_bytes(serialized) assert isinstance(restored, KeyCertificate) assert restored.get_sig_type_code() == 7 assert restored.get_enc_type_code() == 4 assert restored.get_extra_key_data() == extra def test_len_includes_header_and_payload(self): payload = struct.pack("!HH", 7, 4) + b"\xAA" kc = KeyCertificate(payload) assert len(kc) == 3 + len(payload)