A Python port of the Invisible Internet Project (I2P)
1"""NetDB lookup service — query peers for RouterInfo/LeaseSet."""
2
3import enum
4
5
6def _xor_distance(a: bytes, b: bytes) -> bytes:
7 return bytes(x ^ y for x, y in zip(a, b))
8
9
10class LookupState(enum.Enum):
11 NEW = 0
12 QUERYING = 1
13 FOUND = 2
14 NOT_FOUND = 3
15 FAILED = 4
16
17
18class LookupRequest:
19 """A single lookup request for a key in the NetDB."""
20
21 def __init__(self, key: bytes, timeout_ms: int = 10000):
22 self.key = key
23 self.timeout_ms = timeout_ms
24 self.state = LookupState.NEW
25 self.result: bytes | None = None
26 self.error: str | None = None
27 self.queried_peers: set[bytes] = set()
28
29 def start(self):
30 self.state = LookupState.QUERYING
31
32 def set_found(self, data: bytes):
33 self.result = data
34 self.state = LookupState.FOUND
35
36 def set_not_found(self):
37 self.state = LookupState.NOT_FOUND
38
39 def set_failed(self, error: str):
40 self.error = error
41 self.state = LookupState.FAILED
42
43 def add_queried_peer(self, peer_hash: bytes):
44 self.queried_peers.add(peer_hash)
45
46 def already_queried(self, peer_hash: bytes) -> bool:
47 return peer_hash in self.queried_peers
48
49
50class FloodfillSet:
51 """Set of known floodfill routers."""
52
53 def __init__(self):
54 self._floodfills: dict[bytes, bytes | None] = {}
55
56 def add(self, router_hash: bytes, router_info: bytes | None = None):
57 self._floodfills[router_hash] = router_info
58
59 def remove(self, router_hash: bytes):
60 self._floodfills.pop(router_hash, None)
61
62 def is_floodfill(self, router_hash: bytes) -> bool:
63 return router_hash in self._floodfills
64
65 def count(self) -> int:
66 return len(self._floodfills)
67
68 def closest(self, target: bytes, n: int) -> list[bytes]:
69 hashes = list(self._floodfills.keys())
70 hashes.sort(key=lambda h: _xor_distance(h, target))
71 return hashes[:n]