A Python port of the Invisible Internet Project (I2P)
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 }