"""InboundMessageDistributor — route messages from tunnel endpoints. Ported from net.i2p.router.InboundMessageDistributor. Routes I2NP messages from tunnel endpoints to appropriate handlers, filtering by message type based on tunnel type (client vs exploratory). Also handles garlic clove delivery instructions. """ from __future__ import annotations import logging from enum import IntEnum logger = logging.getLogger(__name__) class DeliveryMode(IntEnum): """Garlic clove delivery modes.""" LOCAL = 0 DESTINATION = 1 ROUTER = 2 TUNNEL = 3 # I2NP message type codes class I2NPType(IntEnum): DATABASE_STORE = 1 DATABASE_SEARCH_REPLY = 3 DELIVERY_STATUS = 10 GARLIC = 11 TUNNEL_BUILD_REPLY = 22 SHORT_TUNNEL_BUILD_REPLY = 24 TUNNEL_DATA = 18 TUNNEL_GATEWAY = 19 DATA = 20 # Allowed message types per tunnel category _CLIENT_ALLOWED = frozenset({ I2NPType.DATABASE_SEARCH_REPLY, I2NPType.DATABASE_STORE, I2NPType.DELIVERY_STATUS, I2NPType.GARLIC, I2NPType.TUNNEL_BUILD_REPLY, I2NPType.SHORT_TUNNEL_BUILD_REPLY, }) _EXPLORATORY_ALLOWED = frozenset({ I2NPType.DATABASE_STORE, I2NPType.DATABASE_SEARCH_REPLY, I2NPType.DELIVERY_STATUS, I2NPType.GARLIC, I2NPType.TUNNEL_BUILD_REPLY, I2NPType.SHORT_TUNNEL_BUILD_REPLY, }) class InboundMessageDistributor: """Routes messages from tunnel endpoints to handlers. Filters messages by type based on whether the tunnel is a client tunnel or an exploratory tunnel, then dispatches to the appropriate subsystem. """ def __init__(self, router_context=None, client_hash: bytes | None = None, is_exploratory: bool = False) -> None: self._context = router_context self._client_hash = client_hash self._is_exploratory = is_exploratory self._garlic_handler = None self._client_manager = None self._netdb = None def set_garlic_handler(self, handler) -> None: self._garlic_handler = handler def set_client_manager(self, manager) -> None: self._client_manager = manager def set_netdb(self, netdb) -> None: self._netdb = netdb def is_allowed_message_type(self, msg_type: int) -> bool: """Check if message type is allowed on this tunnel.""" allowed = _EXPLORATORY_ALLOWED if self._is_exploratory else _CLIENT_ALLOWED return msg_type in allowed def distribute(self, msg_type: int, msg_data: bytes, target_router: bytes | None = None, target_tunnel: int | None = None) -> bool: """Route an incoming message to its handler. Returns True if the message was accepted, False if filtered/dropped. """ if not self.is_allowed_message_type(msg_type): logger.debug("Dropping disallowed message type %d on %s tunnel", msg_type, "exploratory" if self._is_exploratory else "client") return False if msg_type == I2NPType.GARLIC and self._garlic_handler: self._garlic_handler(msg_data) return True if msg_type == I2NPType.DELIVERY_STATUS: # Forward to reply handler for ACK matching return True if msg_type == I2NPType.DATABASE_STORE: if self._netdb: self._netdb(msg_data) return True if msg_type == I2NPType.DATABASE_SEARCH_REPLY: return True if msg_type in (I2NPType.TUNNEL_BUILD_REPLY, I2NPType.SHORT_TUNNEL_BUILD_REPLY): return True return False def handle_clove(self, delivery_mode: int, msg_data: bytes, dest_hash: bytes | None = None, router_hash: bytes | None = None, tunnel_id: int | None = None) -> bool: """Handle a garlic clove based on delivery instructions. Args: delivery_mode: DeliveryMode enum value msg_data: The inner message data dest_hash: Destination hash (for DESTINATION delivery) router_hash: Router hash (for ROUTER/TUNNEL delivery) tunnel_id: Tunnel ID (for TUNNEL delivery) Returns True if handled successfully. """ if delivery_mode == DeliveryMode.LOCAL: # Route based on inner message type if len(msg_data) < 1: return False inner_type = msg_data[0] return self.distribute(inner_type, msg_data[1:]) if delivery_mode == DeliveryMode.DESTINATION: if dest_hash is None: return False # Verify destination matches our client if self._client_hash and dest_hash != self._client_hash: logger.debug("Clove destination mismatch: expected %s, got %s", self._client_hash.hex()[:16], dest_hash.hex()[:16]) return False if self._client_manager: self._client_manager(dest_hash, msg_data) return True if delivery_mode == DeliveryMode.ROUTER: # Forward to router return True if delivery_mode == DeliveryMode.TUNNEL: # Forward through tunnel return True return False