A Python port of the Invisible Internet Project (I2P)
1"""Tests for I2NP message routing — InboundMessageHandler, OutboundMessageRouter, MessageDispatcher."""
2
3import struct
4
5import pytest
6
7
8class TestInboundMessageHandlerRegistration:
9 def test_register_and_dispatch_custom_handler(self):
10 from i2p_data.message_router import InboundMessageHandler
11
12 handler = InboundMessageHandler()
13
14 def custom(payload: bytes) -> bytes:
15 return payload[::-1]
16
17 handler.register(99, custom)
18 result = handler.handle(99, b"abc")
19 assert result == b"cba"
20
21 def test_handle_returns_none_for_unregistered_type(self):
22 from i2p_data.message_router import InboundMessageHandler
23
24 handler = InboundMessageHandler()
25 result = handler.handle(255, b"anything")
26 assert result is None
27
28
29class TestInboundDatabaseStoreAndLookup:
30 def test_store_and_lookup(self):
31 from i2p_data.message_router import InboundMessageHandler
32
33 handler = InboundMessageHandler()
34 key = b"\x01" * 32
35 value = b"router info data"
36 payload = key + value
37 handler.on_database_store(payload)
38
39 result = handler.on_database_lookup(key)
40 assert result == value
41
42 def test_lookup_missing_key_returns_none(self):
43 from i2p_data.message_router import InboundMessageHandler
44
45 handler = InboundMessageHandler()
46 key = b"\x02" * 32
47 result = handler.on_database_lookup(key)
48 assert result is None
49
50
51class TestInboundDeliveryStatus:
52 def test_extract_message_id(self):
53 from i2p_data.message_router import InboundMessageHandler
54
55 handler = InboundMessageHandler()
56 msg_id = 0xDEADBEEF
57 payload = msg_id.to_bytes(4, "big") + b"\x00" * 8
58 result = handler.on_delivery_status(payload)
59 assert result == 0xDEADBEEF
60
61 def test_extract_message_id_zero(self):
62 from i2p_data.message_router import InboundMessageHandler
63
64 handler = InboundMessageHandler()
65 payload = (0).to_bytes(4, "big") + b"\x00" * 8
66 result = handler.on_delivery_status(payload)
67 assert result == 0
68
69
70class TestInboundTunnelData:
71 def test_extract_tunnel_id_and_data(self):
72 from i2p_data.message_router import InboundMessageHandler
73
74 handler = InboundMessageHandler()
75 tunnel_id = 42
76 data = b"tunnel payload here"
77 payload = tunnel_id.to_bytes(4, "big") + data
78 tid, tdata = handler.on_tunnel_data(payload)
79 assert tid == 42
80 assert tdata == data
81
82 def test_extract_tunnel_id_empty_data(self):
83 from i2p_data.message_router import InboundMessageHandler
84
85 handler = InboundMessageHandler()
86 payload = (100).to_bytes(4, "big")
87 tid, tdata = handler.on_tunnel_data(payload)
88 assert tid == 100
89 assert tdata == b""
90
91
92class TestInboundGarlic:
93 def test_on_garlic_returns_single_clove(self):
94 from i2p_data.message_router import InboundMessageHandler
95
96 handler = InboundMessageHandler()
97 payload = b"garlic clove data"
98 cloves = handler.on_garlic(payload)
99 assert cloves == [payload]
100
101
102class TestInboundDefaultHandlers:
103 def test_default_handlers_registered(self):
104 from i2p_data.message_router import InboundMessageHandler
105
106 handler = InboundMessageHandler()
107 # DATABASE_STORE=1, DATABASE_LOOKUP=2, DELIVERY_STATUS=10,
108 # GARLIC=11, TUNNEL_DATA=18, TUNNEL_GATEWAY=19
109 assert 1 in handler._handlers
110 assert 2 in handler._handlers
111 assert 10 in handler._handlers
112 assert 11 in handler._handlers
113 assert 18 in handler._handlers
114 assert 19 in handler._handlers
115
116
117class TestOutboundMessageRouterLocal:
118 def test_route_local(self):
119 from i2p_data.message_router import OutboundMessageRouter
120
121 router = OutboundMessageRouter()
122 result = router.route(0, b"local payload")
123 assert result == {"type": "local", "payload": b"local payload"}
124
125
126class TestOutboundMessageRouterRouter:
127 def test_route_router(self):
128 from i2p_data.message_router import OutboundMessageRouter
129
130 router = OutboundMessageRouter()
131 rh = b"\xAA" * 32
132 result = router.route(1, b"data", router_hash=rh)
133 assert result == {"type": "router", "router_hash": rh, "payload": b"data"}
134
135
136class TestOutboundMessageRouterTunnel:
137 def test_route_tunnel(self):
138 from i2p_data.message_router import OutboundMessageRouter
139
140 router = OutboundMessageRouter()
141 gw = b"\xBB" * 32
142 result = router.route(2, b"tdata", tunnel_id=7, gateway=gw)
143 assert result == {
144 "type": "tunnel",
145 "tunnel_id": 7,
146 "gateway": gw,
147 "payload": b"tdata",
148 }
149
150
151class TestOutboundMessageRouterDestination:
152 def test_route_destination(self):
153 from i2p_data.message_router import OutboundMessageRouter
154
155 router = OutboundMessageRouter()
156 dest = b"\xCC" * 32
157 result = router.route(3, b"dest_data", destination=dest)
158 assert result == {
159 "type": "destination",
160 "destination": dest,
161 "payload": b"dest_data",
162 }
163
164
165class TestOutboundMessageRouterUnknown:
166 def test_route_unknown_raises_value_error(self):
167 from i2p_data.message_router import OutboundMessageRouter
168
169 router = OutboundMessageRouter()
170 with pytest.raises(ValueError, match="Unknown delivery type"):
171 router.route(99, b"payload")
172
173
174class TestMessageDispatcher:
175 def test_dispatch_inbound(self):
176 from i2p_data.message_router import (
177 InboundMessageHandler,
178 MessageDispatcher,
179 OutboundMessageRouter,
180 )
181
182 inbound = InboundMessageHandler()
183 outbound = OutboundMessageRouter()
184 dispatcher = MessageDispatcher(inbound, outbound)
185
186 # Dispatch delivery status (type 10)
187 msg_id = 0x12345678
188 payload = msg_id.to_bytes(4, "big") + b"\x00" * 8
189 result = dispatcher.dispatch_inbound(10, payload)
190 assert result == 0x12345678
191
192 def test_dispatch_outbound(self):
193 from i2p_data.message_router import (
194 InboundMessageHandler,
195 MessageDispatcher,
196 OutboundMessageRouter,
197 )
198
199 inbound = InboundMessageHandler()
200 outbound = OutboundMessageRouter()
201 dispatcher = MessageDispatcher(inbound, outbound)
202
203 result = dispatcher.dispatch_outbound(0, b"payload")
204 assert result == {"type": "local", "payload": b"payload"}
205
206 def test_register_inbound_handler(self):
207 from i2p_data.message_router import (
208 InboundMessageHandler,
209 MessageDispatcher,
210 OutboundMessageRouter,
211 )
212
213 inbound = InboundMessageHandler()
214 outbound = OutboundMessageRouter()
215 dispatcher = MessageDispatcher(inbound, outbound)
216
217 def my_handler(payload: bytes) -> str:
218 return "handled"
219
220 dispatcher.register_inbound_handler(200, my_handler)
221 result = dispatcher.dispatch_inbound(200, b"test")
222 assert result == "handled"