A Python port of the Invisible Internet Project (I2P)
1"""NetDB data store — in-memory storage for RouterInfo and LeaseSet entries."""
2
3import enum
4import time
5
6
7class EntryType(enum.IntEnum):
8 ROUTER_INFO = 0
9 LEASE_SET = 1
10
11
12class NetDBEntry:
13 """A single entry in the network database."""
14
15 def __init__(self, key: bytes, entry_type: EntryType, data: bytes,
16 received_ms: int, expiration_ms: int | None = None):
17 self.key = key
18 self.entry_type = entry_type
19 self.data = data
20 self.received_ms = received_ms
21 self.expiration_ms = expiration_ms
22
23 def is_expired(self, now_ms: int | None = None) -> bool:
24 if self.expiration_ms is None:
25 return False
26 if now_ms is None:
27 now_ms = int(time.time() * 1000)
28 return now_ms >= self.expiration_ms
29
30
31def _xor_distance(a: bytes, b: bytes) -> bytes:
32 return bytes(x ^ y for x, y in zip(a, b))
33
34
35class DataStore:
36 """In-memory key-value store for NetDB entries."""
37
38 def __init__(self):
39 self._entries: dict[bytes, NetDBEntry] = {}
40
41 def put(self, entry: NetDBEntry):
42 self._entries[entry.key] = entry
43
44 def get(self, key: bytes) -> NetDBEntry | None:
45 return self._entries.get(key)
46
47 def remove(self, key: bytes):
48 self._entries.pop(key, None)
49
50 def count(self) -> int:
51 return len(self._entries)
52
53 def count_by_type(self, entry_type: EntryType) -> int:
54 return sum(1 for e in self._entries.values() if e.entry_type == entry_type)
55
56 def remove_expired(self, now_ms: int | None = None):
57 expired = [k for k, e in self._entries.items() if e.is_expired(now_ms)]
58 for k in expired:
59 del self._entries[k]
60
61 def get_all(self) -> list[NetDBEntry]:
62 """Return all entries."""
63 return list(self._entries.values())
64
65 def get_all_keys(self) -> list[bytes]:
66 return list(self._entries.keys())
67
68 def closest_keys(self, target: bytes, n: int) -> list[bytes]:
69 keys = list(self._entries.keys())
70 keys.sort(key=lambda k: _xor_distance(k, target))
71 return keys[:n]