"""Tunnel build pipeline wiring. Ported from: net.i2p.router.tunnel.pool.BuildHandler (conceptual) Connects peer selection, hop configuration generation, build execution, crypto registration, and tunnel pool management into a single pipeline. """ from __future__ import annotations import os import time from i2p_data.tunnel import TunnelId, HopConfig from i2p_tunnel.build_executor import TunnelBuildExecutor, TunnelManager from i2p_tunnel.builder import TunnelEntry from i2p_tunnel.data_handler import TunnelCryptoRegistry from i2p_peer.profile import PeerSelector class TunnelBuildPipeline: """Wire together tunnel building components into a cohesive pipeline. Orchestrates the full tunnel build lifecycle: 1. Generate HopConfigs with random crypto keys for selected peers 2. Delegate to TunnelBuildExecutor to create build messages 3. Process replies — on acceptance, register keys and pool the tunnel 4. Report pool maintenance status """ def __init__( self, build_executor: TunnelBuildExecutor, tunnel_manager: TunnelManager, crypto_registry: TunnelCryptoRegistry, peer_selector: PeerSelector | None = None, ) -> None: self._executor = build_executor self._manager = tunnel_manager self._registry = crypto_registry self._peer_selector = peer_selector def build_tunnel(self, peer_profiles: list, is_inbound: bool = True) -> dict: """Generate HopConfigs and build a tunnel request message. Parameters ---------- peer_profiles: List of PeerProfile objects representing the peers to use as hops, in order from gateway to endpoint. is_inbound: True for inbound tunnels, False for outbound. Returns ------- dict ``{"hop_configs": [...], "build_msg": }`` """ hop_configs = [] public_keys = [] for i, profile in enumerate(peer_profiles): # Generate random tunnel IDs and crypto keys for each hop. receive_tid = TunnelId(int.from_bytes(os.urandom(4), "big") % (2**32 - 1) + 1) send_tid = TunnelId(int.from_bytes(os.urandom(4), "big") % (2**32 - 1) + 1) layer_key = os.urandom(32) iv_key = os.urandom(32) reply_key = os.urandom(32) reply_iv = os.urandom(16) receive_key = os.urandom(32) send_key = os.urandom(32) hop = HopConfig( receive_tunnel_id=receive_tid, send_tunnel_id=send_tid, receive_key=receive_key, send_key=send_key, iv_key=iv_key, reply_key=reply_key, reply_iv=reply_iv, layer_key=layer_key, ) hop_configs.append(hop) # In a full implementation, the public key would come from # the peer's RouterInfo. For now, generate a placeholder # 256-byte key so the executor has something to work with. public_keys.append(os.urandom(256)) build_msg = self._executor.build_tunnel(hop_configs, public_keys, is_inbound) return {"hop_configs": hop_configs, "build_msg": build_msg} def process_reply( self, reply_result, hop_configs: list, tunnel_id: int, is_inbound: bool, expiration_ms: int, ) -> TunnelEntry | None: """Process a build reply and register the tunnel if accepted. Parameters ---------- reply_result: A dict with an ``"accepted"`` boolean key indicating whether all hops accepted the build request. hop_configs: The HopConfig list from build_tunnel. tunnel_id: The numeric tunnel identifier for this tunnel. is_inbound: True for inbound tunnels, False for outbound. expiration_ms: Absolute expiration time in milliseconds since epoch. Returns ------- TunnelEntry | None A TunnelEntry if all hops accepted; None if any rejected. """ if not reply_result.get("accepted", False): return None now_ms = int(time.time() * 1000) entry = TunnelEntry( tunnel_id=TunnelId(tunnel_id), gateway=os.urandom(32), length=len(hop_configs), creation_time=now_ms, expiration=expiration_ms, ) # Register each hop's crypto keys in the registry. num_hops = len(hop_configs) for i, hop in enumerate(hop_configs): is_endpoint = (i == num_hops - 1) self._registry.register( tunnel_id=int(hop.receive_tunnel_id), layer_key=hop.layer_key, iv_key=hop.iv_key, is_endpoint=is_endpoint, ) # Add the tunnel to the appropriate pool. self._manager.add_tunnel(entry, is_inbound=is_inbound) return entry def maintain_pools(self) -> dict: """Check tunnel pool status against targets. Returns ------- dict ``{"needs_inbound": bool, "needs_outbound": bool, "inbound_count": int, "outbound_count": int}`` """ return { "needs_inbound": self._manager.needs_more_inbound(), "needs_outbound": self._manager.needs_more_outbound(), "inbound_count": self._manager.inbound_count(), "outbound_count": self._manager.outbound_count(), }