A Python port of the Invisible Internet Project (I2P)
at main 147 lines 6.2 kB view raw
1"""Byte-level comparison: our RI serialization vs Java peer RI.""" 2import asyncio 3import hashlib 4import io 5import struct 6import sys 7 8sys.path.insert(0, "src") 9 10from i2p_data.router import RouterInfo, RouterAddress, _serialize_mapping, _parse_mapping 11from i2p_data.keys_and_cert import KeysAndCert 12from i2p_netdb.reseed import ReseedClient 13from i2p_router.identity import ( 14 RouterKeyBundle, 15 create_full_router_identity, 16) 17 18 19def hexdump_ri(label, ri_bytes): 20 """Parse and hexdump each section of a RouterInfo.""" 21 print(f"\n=== {label} (total {len(ri_bytes)} bytes) ===") 22 23 stream = io.BytesIO(ri_bytes) 24 25 # Identity: 256 + 128 + cert 26 pub_area = stream.read(256) 27 sig_area = stream.read(128) 28 cert_header = stream.read(3) 29 cert_type, cert_len = struct.unpack("!BH", cert_header) 30 cert_payload = stream.read(cert_len) if cert_len > 0 else b"" 31 identity_end = stream.tell() 32 33 print(f"Identity ({identity_end} bytes):") 34 print(f" Pub area (256): ...{pub_area[-36:].hex()}") 35 print(f" Sig area (128): ...{sig_area[-36:].hex()}") 36 print(f" Cert: type={cert_type} len={cert_len} payload={cert_payload.hex()}") 37 38 # Published 39 published = struct.unpack("!Q", stream.read(8))[0] 40 print(f"Published: {published} (0x{published:016x})") 41 42 # Address count 43 addr_count = struct.unpack("!B", stream.read(1))[0] 44 print(f"Address count: {addr_count}") 45 46 # Addresses 47 for i in range(addr_count): 48 addr_start = stream.tell() 49 cost = struct.unpack("!B", stream.read(1))[0] 50 expiration = struct.unpack("!Q", stream.read(8))[0] 51 transport_len = struct.unpack("!B", stream.read(1))[0] 52 transport = stream.read(transport_len).decode("utf-8") 53 props_len = struct.unpack("!H", stream.read(2))[0] 54 props_raw = stream.read(props_len) if props_len > 0 else b"" 55 addr_end = stream.tell() 56 print(f"Address[{i}] ({addr_end - addr_start} bytes):") 57 print(f" cost={cost} exp={expiration} transport='{transport}'") 58 print(f" props_len={props_len}") 59 print(f" props_raw ({len(props_raw)} bytes): {props_raw.hex()}") 60 # Parse and reserialize to compare 61 parsed_props = _parse_mapping(props_raw) 62 reserialized = _serialize_mapping(parsed_props) 63 print(f" props_parsed: {parsed_props}") 64 print(f" props_reserialized ({len(reserialized)} bytes): {reserialized.hex()}") 65 print(f" MATCH: {props_raw == reserialized}") 66 if props_raw != reserialized: 67 # Find first difference 68 for j in range(min(len(props_raw), len(reserialized))): 69 if props_raw[j] != reserialized[j]: 70 print(f" FIRST DIFF at byte {j}: original=0x{props_raw[j]:02x} reserialized=0x{reserialized[j]:02x}") 71 print(f" Context: original[{max(0,j-5)}:{j+10}]={props_raw[max(0,j-5):j+10].hex()}") 72 print(f" Context: reserial[{max(0,j-5)}:{j+10}]={reserialized[max(0,j-5):j+10].hex()}") 73 break 74 if len(props_raw) != len(reserialized): 75 print(f" LENGTH DIFF: original={len(props_raw)} reserialized={len(reserialized)}") 76 77 # Peer size 78 peer_size = struct.unpack("!B", stream.read(1))[0] 79 print(f"Peer size: {peer_size}") 80 81 # Options 82 opts_len = struct.unpack("!H", stream.read(2))[0] 83 opts_raw = stream.read(opts_len) if opts_len > 0 else b"" 84 print(f"Options (len={opts_len}):") 85 print(f" opts_raw ({len(opts_raw)} bytes): {opts_raw.hex()}") 86 parsed_opts = _parse_mapping(opts_raw) 87 reserialized_opts = _serialize_mapping(parsed_opts) 88 print(f" opts_parsed: {parsed_opts}") 89 print(f" opts_reserialized ({len(reserialized_opts)} bytes): {reserialized_opts.hex()}") 90 print(f" MATCH: {opts_raw == reserialized_opts}") 91 if opts_raw != reserialized_opts: 92 for j in range(min(len(opts_raw), len(reserialized_opts))): 93 if opts_raw[j] != reserialized_opts[j]: 94 print(f" FIRST DIFF at byte {j}: original=0x{opts_raw[j]:02x} reserialized=0x{reserialized_opts[j]:02x}") 95 print(f" Context orig: {opts_raw[max(0,j-5):j+10].hex()}") 96 print(f" Context rser: {reserialized_opts[max(0,j-5):j+10].hex()}") 97 break 98 if len(opts_raw) != len(reserialized_opts): 99 print(f" LENGTH DIFF: original={len(opts_raw)} reserialized={len(reserialized_opts)}") 100 101 # Signature 102 sig_pos = stream.tell() 103 sig = stream.read() 104 print(f"Signature ({len(sig)} bytes at offset {sig_pos}): {sig.hex()}") 105 106 # Verify signable bytes roundtrip 107 signable_end = sig_pos 108 signable_raw = ri_bytes[:signable_end] 109 ri_obj = RouterInfo.from_bytes(ri_bytes) 110 signable_computed = ri_obj._signable_bytes() 111 print(f"\nSignable bytes comparison:") 112 print(f" Raw from wire: {len(signable_raw)} bytes") 113 print(f" Computed: {len(signable_computed)} bytes") 114 print(f" MATCH: {signable_raw == signable_computed}") 115 if signable_raw != signable_computed: 116 for j in range(min(len(signable_raw), len(signable_computed))): 117 if signable_raw[j] != signable_computed[j]: 118 print(f" FIRST DIFF at byte {j}: wire=0x{signable_raw[j]:02x} computed=0x{signable_computed[j]:02x}") 119 print(f" Wire context [{j-10}:{j+10}]: {signable_raw[max(0,j-10):j+10].hex()}") 120 print(f" Computed context[{j-10}:{j+10}]: {signable_computed[max(0,j-10):j+10].hex()}") 121 break 122 123 print(f" verify(): {ri_obj.verify()}") 124 125 126async def main(): 127 # Generate our RI 128 bundle = RouterKeyBundle.generate() 129 identity, ri = create_full_router_identity(bundle, "0.0.0.0", 9000) 130 our_ri_bytes = ri.to_bytes() 131 132 hexdump_ri("OUR ROUTERINFO", our_ri_bytes) 133 134 # Get a Java peer RI 135 print("\n\nReseeding...") 136 client = ReseedClient(target_count=5, min_servers=1, timeout=15) 137 ri_list = await client.reseed() 138 139 for i, peer_bytes in enumerate(ri_list[:3]): 140 try: 141 hexdump_ri(f"JAVA PEER RI #{i+1}", peer_bytes) 142 except Exception as e: 143 print(f"Error parsing peer RI #{i+1}: {e}") 144 145 146if __name__ == "__main__": 147 asyncio.run(main())