"""Peer selector for tunnel building — selects peers by tier and generates hop configs. Ported from net.i2p.router.tunnel.pool.TunnelPeerSelector. """ from __future__ import annotations import os import struct from typing import TYPE_CHECKING from i2p_peer.hop_config import TunnelHopConfig from i2p_peer.organizer import PeerTier, _TIER_ORDER if TYPE_CHECKING: from i2p_peer.organizer import ProfileOrganizer class PeerSelector: """Selects peers for tunnel building based on profile scores.""" def __init__(self, organizer: ProfileOrganizer): self._organizer = organizer def select_peers(self, count: int, exclude: set[bytes] | None = None) -> list[bytes]: """Select count peers, preferring higher-tier peers. Selection strategy: 1. Walk tiers from FAST down to STANDARD, collecting eligible peers. 2. Exclude any in the exclude set. 3. Return top ``count`` peers (tier order preserved). """ exclude = exclude or set() selected: list[bytes] = [] # Walk tiers best-to-worst, skip FAILING and BANNED eligible_tiers = [PeerTier.FAST, PeerTier.HIGH_CAPACITY, PeerTier.STANDARD] for tier in eligible_tiers: tier_hashes = self._organizer._tiers.get(tier, set()) for h in tier_hashes: if h not in exclude: selected.append(h) if len(selected) >= count: return selected[:count] return selected[:count] def select_hops(self, length: int, exclude: set[bytes] | None = None) -> list[TunnelHopConfig]: """Select peers and generate full hop configs for tunnel building. - First hop is gateway (is_gateway=True) - Last hop is endpoint (is_endpoint=True) - A single hop is both gateway and endpoint - Each hop gets random keys generated with os.urandom() - receive_tunnel_id is a random positive uint32 """ peer_hashes = self.select_peers(length, exclude=exclude) if not peer_hashes: return [] hops: list[TunnelHopConfig] = [] for i, peer_hash in enumerate(peer_hashes): # Random positive tunnel ID (1 to 2^32-1) tid = struct.unpack("!I", os.urandom(4))[0] if tid == 0: tid = 1 # ensure positive hop = TunnelHopConfig( peer_hash=peer_hash, receive_tunnel_id=tid, layer_key=os.urandom(32), iv_key=os.urandom(32), reply_key=os.urandom(32), reply_iv=os.urandom(16), is_gateway=(i == 0), is_endpoint=(i == len(peer_hashes) - 1), ) hops.append(hop) return hops