"""OutboundCache — per-destination send state caching. Ported from net.i2p.router.message.OutboundClientMessageOneShotJob (internal cache fields). Caches lease, tunnel, and reply state for destination pairs to avoid redundant lookups and rate-limit ACK requests. """ from __future__ import annotations from dataclasses import dataclass @dataclass(frozen=True) class HashPair: """Immutable pair of (from_hash, to_hash) used as cache key.""" from_hash: bytes to_hash: bytes def __eq__(self, other: object) -> bool: if not isinstance(other, HashPair): return NotImplemented return self.from_hash == other.from_hash and self.to_hash == other.to_hash def __hash__(self) -> int: return hash((self.from_hash, self.to_hash)) class OutboundCache: """Per-destination send state cache. Caches recently used leases, tunnels, and ACK timing to optimize outbound message routing. """ REPLY_REQUEST_INTERVAL_MS = 60_000 # min 60s between ACK requests def __init__(self) -> None: self._lease_cache: dict[HashPair, object] = {} self._tunnel_cache: dict[HashPair, object] = {} self._leaseset_acked: dict[HashPair, int] = {} # pair -> ack timestamp self._last_reply_request: dict[HashPair, int] = {} # pair -> request timestamp # --- Lease caching --- def get_cached_lease(self, pair: HashPair) -> object | None: return self._lease_cache.get(pair) def set_cached_lease(self, pair: HashPair, lease: object) -> None: self._lease_cache[pair] = lease def clear_cached_lease(self, pair: HashPair) -> None: self._lease_cache.pop(pair, None) # --- Tunnel caching --- def get_cached_tunnel(self, pair: HashPair) -> object | None: return self._tunnel_cache.get(pair) def set_cached_tunnel(self, pair: HashPair, tunnel: object) -> None: self._tunnel_cache[pair] = tunnel def clear_cached_tunnel(self, pair: HashPair) -> None: self._tunnel_cache.pop(pair, None) # --- Reply request rate limiting --- def should_request_reply(self, pair: HashPair, now_ms: int) -> bool: """True if enough time has passed since last reply request.""" last = self._last_reply_request.get(pair) if last is None: return True return (now_ms - last) >= self.REPLY_REQUEST_INTERVAL_MS def record_reply_request(self, pair: HashPair, now_ms: int) -> None: self._last_reply_request[pair] = now_ms # --- LeaseSet ACK tracking --- def is_leaseset_acked(self, pair: HashPair) -> bool: return pair in self._leaseset_acked def record_leaseset_ack(self, pair: HashPair, now_ms: int) -> None: self._leaseset_acked[pair] = now_ms def clear_leaseset_ack(self, pair: HashPair) -> None: self._leaseset_acked.pop(pair, None)