A Python port of the Invisible Internet Project (I2P)
at main 61 lines 2.2 kB view raw
1"""MessageValidator — reject expired, far-future, and duplicate I2NP messages. 2 3Ported from net.i2p.router.MessageValidator. 4 5Uses a DecayingBloomFilter to detect duplicate messages. Messages are 6identified by their (message_id XOR truncated_expiration) fingerprint. 7""" 8 9from __future__ import annotations 10 11import struct 12import time 13 14from i2p_router.bloom_filter import DecayingBloomFilter 15 16 17class MessageValidator: 18 """Validate inbound I2NP messages for expiration and duplicates.""" 19 20 # Java I2P uses 65 seconds of clock fudge 21 CLOCK_FUDGE_FACTOR_MS = 65_000 22 23 def __init__(self, bloom_filter: DecayingBloomFilter | None = None) -> None: 24 if bloom_filter is None: 25 bloom_filter = DecayingBloomFilter( 26 "msg-validator", duration_seconds=600, expected_entries=50000 27 ) 28 self._bloom = bloom_filter 29 30 def validate_message( 31 self, message_id: int, expiration_ms: int, now_ms: int | None = None 32 ) -> bool: 33 """Return True if the message should be accepted. 34 35 Rejects expired, far-future, and duplicate messages. 36 """ 37 if now_ms is None: 38 now_ms = int(time.time() * 1000) 39 40 if self._is_expired(expiration_ms, now_ms): 41 return False 42 if self._is_too_far_future(expiration_ms, now_ms): 43 return False 44 if self._is_duplicate(message_id, expiration_ms): 45 return False 46 return True 47 48 def _is_expired(self, expiration_ms: int, now_ms: int) -> bool: 49 """True if message has expired (with 1.5x fudge).""" 50 return now_ms > expiration_ms + int(self.CLOCK_FUDGE_FACTOR_MS * 1.5) 51 52 def _is_too_far_future(self, expiration_ms: int, now_ms: int) -> bool: 53 """True if expiration is unreasonably far in the future.""" 54 return expiration_ms > now_ms + self.CLOCK_FUDGE_FACTOR_MS * 4 55 56 def _is_duplicate(self, message_id: int, expiration_ms: int) -> bool: 57 """True if we've already seen this message.""" 58 # Fingerprint: XOR message_id with truncated expiration 59 exp_trunc = expiration_ms & 0xFFFFFFFF 60 fingerprint = struct.pack("!I", message_id ^ exp_trunc) 61 return self._bloom.add(fingerprint)