""" Random forwarding routing protocol. Baseline algorithm: randomly select ONE neighbor to forward to. This demonstrates the value of gradient-based routing. """ import random from typing import Dict, Optional from dataclasses import dataclass from sim.core.packet import Packet, PacketType from sim import config @dataclass class NeighborInfo: """Information about a neighbor node.""" node_id: int rssi: float last_hello_time: float class RandomForwardRouting: """ Random forwarding routing protocol. Each node maintains: - neighbors: Dict of known neighbors - For each packet, randomly selects ONE neighbor to forward to This is a baseline to demonstrate why gradient routing is better. """ def __init__(self, node_id: int, is_sink: bool = False): """ Initialize routing. Args: node_id: This node's ID is_sink: Whether this node is the sink """ self.node_id = node_id self.is_sink = is_sink # Routing state self.parent: Optional[int] = None # Randomly selected per packet self.neighbors: Dict[int, NeighborInfo] = {} # Sequence number for HELLO messages self.hello_seq = 0 # Cost (for compatibility with metrics) self.cost = 0 if is_sink else 1 def reset(self): """Reset routing state.""" self.neighbors.clear() self.hello_seq = 0 self.cost = 0 if self.is_sink else 1 self.parent = None def create_hello_packet(self) -> Packet: """ Create a HELLO packet for neighbor discovery. Returns: HELLO packet with node ID """ packet = Packet( type=PacketType.HELLO, src=self.node_id, dst=-1, # Broadcast seq=self.hello_seq, hop=0, payload=str(self.node_id), ) self.hello_seq += 1 return packet def process_hello(self, packet: Packet, rssi: float) -> bool: """ Process received HELLO packet. Track neighbors but don't calculate cost. Args: packet: Received HELLO packet rssi: RSSI of received signal Returns: True if neighbor list changed """ # Update neighbor info old_neighbors = len(self.neighbors) self.neighbors[packet.src] = NeighborInfo( node_id=packet.src, rssi=rssi, last_hello_time=rssi, # Store time in rssi field ) return len(self.neighbors) != old_neighbors def get_next_hop(self, packet: Packet = None) -> Optional[int]: """ Randomly select ONE neighbor to forward to. Args: packet: The packet to forward (unused in random routing) Returns: Random neighbor ID, or None if no neighbors """ if not self.neighbors: return None # Randomly select one neighbor neighbor_ids = list(self.neighbors.keys()) return random.choice(neighbor_ids) def is_route_valid(self) -> bool: """Check if routing is valid.""" return len(self.neighbors) > 0 def cleanup_stale_neighbors(self, current_time: float, timeout: float = 30.0): """Remove neighbors that haven't sent HELLO recently.""" stale = [ nid for nid, info in self.neighbors.items() if current_time - info.last_hello_time > timeout ] for nid in stale: del self.neighbors[nid] def get_routing_table(self) -> dict: """Get routing table for debugging/visualization.""" return { "node_id": self.node_id, "is_sink": self.is_sink, "cost": self.cost, "parent": self.parent, "neighbors": { nid: {"rssi": round(info.rssi, 2)} for nid, info in self.neighbors.items() }, "algorithm": "random_forward", }