A Python port of the Invisible Internet Project (I2P)
at main 218 lines 7.3 kB view raw
1"""RouterContext — central coordinator that owns all subsystem instances. 2 3Ported from net.i2p.router.RouterContext. 4 5The RouterContext wires together crypto, messaging, tunnel, and NetDB 6subsystems, providing a single entry point for inbound/outbound message 7processing, tunnel management, and network database operations. 8""" 9 10from __future__ import annotations 11 12import os 13 14from i2p_crypto.session_key_manager import SessionKeyManager 15from i2p_crypto.garlic_crypto import GarlicEncryptor, GarlicDecryptor 16from i2p_data.message_router import ( 17 InboundMessageHandler, 18 OutboundMessageRouter, 19 MessageDispatcher, 20) 21from i2p_data.garlic_handler import GarlicMessageHandler 22from i2p_netdb.datastore import DataStore 23from i2p_netdb.netdb_handler import NetDBHandler 24from i2p_tunnel.data_handler import TunnelCryptoRegistry, TunnelDataHandler 25from i2p_tunnel.build_executor import TunnelManager 26 27 28class RouterContext: 29 """Central coordinator that owns all subsystem instances and wires them together. 30 31 Parameters 32 ---------- 33 router_hash: 34 32-byte router identity hash. If None, a random 32-byte hash is generated. 35 """ 36 37 def __init__(self, router_hash: bytes | None = None) -> None: 38 self.router_hash = router_hash if router_hash is not None else os.urandom(32) 39 40 # Crypto subsystems 41 self.session_key_mgr = SessionKeyManager() 42 self.garlic_encryptor = GarlicEncryptor() 43 self.garlic_decryptor = GarlicDecryptor() 44 45 # Message routing subsystems 46 self.inbound_handler = InboundMessageHandler() 47 self.outbound_router = OutboundMessageRouter() 48 self.message_dispatcher = MessageDispatcher( 49 self.inbound_handler, self.outbound_router 50 ) 51 52 # NetDB subsystems 53 self.datastore = DataStore() 54 self.netdb_handler = NetDBHandler(self.datastore) 55 56 # Tunnel subsystems 57 self.crypto_registry = TunnelCryptoRegistry() 58 self.tunnel_data_handler = TunnelDataHandler(self.crypto_registry) 59 self.tunnel_manager = TunnelManager() 60 61 # Garlic message handler 62 self.garlic_handler = GarlicMessageHandler( 63 self.session_key_mgr, self.garlic_decryptor 64 ) 65 66 # Wire all handlers together 67 self._wire_handlers() 68 69 def _wire_handlers(self) -> None: 70 """Register message-type handlers on the inbound handler.""" 71 # GARLIC (type 11) 72 self.inbound_handler.register(11, self.garlic_handler.handle) 73 # DATABASE_STORE (type 1) 74 self.inbound_handler.register(1, self._handle_db_store) 75 # DATABASE_LOOKUP (type 2) 76 self.inbound_handler.register(2, self._handle_db_lookup) 77 # TUNNEL_DATA (type 18) 78 self.inbound_handler.register(18, self._handle_tunnel_data) 79 80 def _handle_db_store(self, payload: bytes) -> bool: 81 """Adapter: extract key(32) + data from payload and delegate to netdb_handler.""" 82 key = payload[:32] 83 data = payload[32:] 84 return self.netdb_handler.handle_store(key, data) 85 86 def _handle_db_lookup(self, payload: bytes): 87 """Adapter: extract key(32) from payload and delegate to netdb_handler.""" 88 key = payload[:32] 89 return self.netdb_handler.handle_lookup(key) 90 91 def _handle_tunnel_data(self, payload: bytes) -> dict: 92 """Adapter: extract tunnel_id(4) + data from payload and delegate to tunnel_data_handler.""" 93 tunnel_id = int.from_bytes(payload[:4], "big") 94 data = payload[4:] 95 return self.tunnel_data_handler.handle_inbound(tunnel_id, data) 96 97 def process_inbound(self, message_type: int, payload: bytes): 98 """Dispatch an inbound I2NP message to the appropriate handler. 99 100 Parameters 101 ---------- 102 message_type: 103 I2NP message type code. 104 payload: 105 Raw message payload bytes. 106 107 Returns 108 ------- 109 The result from the registered handler, or None if no handler is registered. 110 """ 111 return self.message_dispatcher.dispatch_inbound(message_type, payload) 112 113 def route_outbound(self, delivery_type: int, payload: bytes, **kwargs) -> dict: 114 """Route an outbound message based on delivery type. 115 116 Parameters 117 ---------- 118 delivery_type: 119 Delivery type code (0=LOCAL, 1=ROUTER, 2=TUNNEL, 3=DESTINATION). 120 payload: 121 Message payload bytes. 122 **kwargs: 123 Additional routing parameters (router_hash, tunnel_id, gateway, destination). 124 125 Returns 126 ------- 127 dict 128 Routing descriptor with type and payload fields. 129 """ 130 return self.message_dispatcher.dispatch_outbound(delivery_type, payload, **kwargs) 131 132 def store_netdb_entry(self, key: bytes, data: bytes) -> bool: 133 """Store a key-value entry in the network database. 134 135 Parameters 136 ---------- 137 key: 138 32-byte entry key. 139 data: 140 Entry data bytes. 141 142 Returns 143 ------- 144 bool 145 True on success. 146 """ 147 return self.netdb_handler.handle_store(key, data) 148 149 def lookup_netdb_entry(self, key: bytes) -> bytes | None: 150 """Look up an entry in the network database. 151 152 Parameters 153 ---------- 154 key: 155 32-byte entry key. 156 157 Returns 158 ------- 159 bytes or None 160 The entry data, or None if not found. 161 """ 162 return self.netdb_handler.handle_lookup(key) 163 164 def register_tunnel( 165 self, 166 tunnel_id: int, 167 layer_key: bytes, 168 iv_key: bytes, 169 is_endpoint: bool = False, 170 ) -> None: 171 """Register a tunnel in the crypto registry. 172 173 Parameters 174 ---------- 175 tunnel_id: 176 Numeric tunnel identifier. 177 layer_key: 178 32-byte AES layer key. 179 iv_key: 180 Key for IV derivation. 181 is_endpoint: 182 True if this router is the tunnel endpoint. 183 """ 184 self.crypto_registry.register(tunnel_id, layer_key, iv_key, is_endpoint) 185 186 def process_tunnel_data(self, tunnel_id: int, encrypted_data: bytes) -> dict: 187 """Decrypt one layer of inbound tunnel data and decide routing. 188 189 Parameters 190 ---------- 191 tunnel_id: 192 The tunnel ID this data arrived on. 193 encrypted_data: 194 Encrypted tunnel data (multiple of 16 bytes). 195 196 Returns 197 ------- 198 dict 199 Routing decision: deliver, forward, or unknown. 200 """ 201 return self.tunnel_data_handler.handle_inbound(tunnel_id, encrypted_data) 202 203 def get_status(self) -> dict: 204 """Return a status snapshot of the router. 205 206 Returns 207 ------- 208 dict 209 Status dictionary with router_hash, tunnels_registered, 210 netdb_entries, inbound_count, and outbound_count. 211 """ 212 return { 213 "router_hash": self.router_hash.hex(), 214 "tunnels_registered": len(self.crypto_registry.registered_tunnels()), 215 "netdb_entries": self.datastore.count(), 216 "inbound_count": self.tunnel_manager.inbound_count(), 217 "outbound_count": self.tunnel_manager.outbound_count(), 218 }