Skip to main content

rns_net/interface/
backbone.rs

1//! Backbone TCP mesh interface using cross-platform polling.
2//!
3//! Server mode: listens on a TCP port, accepts peer connections, spawns
4//! dynamic per-peer interfaces. Uses a single poll thread to multiplex
5//! all client sockets. HDLC framing for packet boundaries.
6//!
7//! Client mode: connects to a remote backbone server, single TCP connection
8//! with HDLC framing. Reconnects on disconnect.
9//!
10//! Matches Python `BackboneInterface.py`.
11
12use std::collections::HashMap;
13use std::hash::{BuildHasher, Hasher};
14use std::io::{self, Read, Write};
15use std::net::{IpAddr, Shutdown, TcpListener, TcpStream, ToSocketAddrs};
16use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
17use std::sync::{Arc, Mutex};
18use std::thread;
19use std::time::{Duration, Instant};
20
21use polling::{Event as PollEvent, Events, Poller};
22use socket2::{SockRef, TcpKeepalive};
23
24use rns_core::constants;
25use rns_core::transport::types::{IngressControlConfig, InterfaceId, InterfaceInfo};
26
27use crate::event::{Event, EventSender};
28use crate::hdlc;
29use crate::interface::{
30    lock_or_recover, InterfaceConfigData, InterfaceFactory, StartContext, StartResult, Writer,
31};
32use crate::BackbonePeerStateEntry;
33
34/// HW_MTU: 1 MB (matches Python BackboneInterface.HW_MTU)
35#[allow(dead_code)]
36const HW_MTU: usize = 1_048_576;
37
38/// Configuration for a backbone interface.
39#[derive(Debug, Clone)]
40pub struct BackboneConfig {
41    pub name: String,
42    pub listen_ip: String,
43    pub listen_port: u16,
44    pub interface_id: InterfaceId,
45    pub mode: u8,
46    pub recursive_prs: bool,
47    pub announces_from_internal: bool,
48    pub max_connections: Option<usize>,
49    pub idle_timeout: Option<Duration>,
50    pub write_stall_timeout: Option<Duration>,
51    pub abuse: BackboneAbuseConfig,
52    pub ingress_control: IngressControlConfig,
53    pub runtime: Arc<Mutex<BackboneServerRuntime>>,
54    pub peer_state: Arc<Mutex<BackbonePeerMonitor>>,
55}
56
57/// Configurable behavior-based abuse detection for inbound peers.
58#[derive(Debug, Clone, Default)]
59pub struct BackboneAbuseConfig {
60    pub max_penalty_duration: Option<Duration>,
61}
62
63/// Live runtime state for a backbone server interface.
64#[derive(Debug, Clone)]
65pub struct BackboneServerRuntime {
66    pub max_connections: Option<usize>,
67    pub idle_timeout: Option<Duration>,
68    pub write_stall_timeout: Option<Duration>,
69    pub abuse: BackboneAbuseConfig,
70}
71
72impl BackboneServerRuntime {
73    pub fn from_config(config: &BackboneConfig) -> Self {
74        Self {
75            max_connections: config.max_connections,
76            idle_timeout: config.idle_timeout,
77            write_stall_timeout: config.write_stall_timeout,
78            abuse: config.abuse.clone(),
79        }
80    }
81}
82
83#[derive(Debug, Clone)]
84pub struct BackboneRuntimeConfigHandle {
85    pub interface_name: String,
86    pub runtime: Arc<Mutex<BackboneServerRuntime>>,
87    pub startup: BackboneServerRuntime,
88}
89
90#[derive(Debug, Clone)]
91pub struct BackbonePeerStateHandle {
92    pub interface_id: InterfaceId,
93    pub interface_name: String,
94    pub peer_state: Arc<Mutex<BackbonePeerMonitor>>,
95}
96
97impl Default for BackboneConfig {
98    fn default() -> Self {
99        let mut config = BackboneConfig {
100            name: String::new(),
101            listen_ip: "0.0.0.0".into(),
102            listen_port: 0,
103            interface_id: InterfaceId(0),
104            mode: constants::MODE_FULL,
105            recursive_prs: false,
106            announces_from_internal: true,
107            max_connections: None,
108            idle_timeout: None,
109            write_stall_timeout: None,
110            abuse: BackboneAbuseConfig::default(),
111            ingress_control: IngressControlConfig::enabled(),
112            runtime: Arc::new(Mutex::new(BackboneServerRuntime {
113                max_connections: None,
114                idle_timeout: None,
115                write_stall_timeout: None,
116                abuse: BackboneAbuseConfig::default(),
117            })),
118            peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
119        };
120        let startup = BackboneServerRuntime::from_config(&config);
121        config.runtime = Arc::new(Mutex::new(startup));
122        config
123    }
124}
125
126/// Maximum pending buffer size per client (512 KB). Clients exceeding this are
127/// disconnected to prevent unbounded memory growth from slow readers.
128const MAX_PENDING_BYTES: usize = 512 * 1024;
129
130/// Writer that sends HDLC-framed data over a cloned TCP stream (server mode).
131struct BackboneWriter {
132    stream: TcpStream,
133    runtime: Arc<Mutex<BackboneServerRuntime>>,
134    interface_name: String,
135    interface_id: InterfaceId,
136    event_tx: EventSender,
137    pending: Vec<u8>,
138    stall_started: Option<Instant>,
139    disconnect_notified: bool,
140    write_stall_flag: Arc<AtomicBool>,
141}
142
143impl Writer for BackboneWriter {
144    fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
145        let write_stall_timeout =
146            lock_or_recover(&self.runtime, "backbone runtime").write_stall_timeout;
147        if !self.pending.is_empty() {
148            self.flush_pending(write_stall_timeout)?;
149            if !self.pending.is_empty() {
150                return Err(io::Error::new(
151                    io::ErrorKind::WouldBlock,
152                    "backbone writer still stalled",
153                ));
154            }
155        }
156
157        let frame = hdlc::frame(data);
158        self.write_buffer(&frame, write_stall_timeout)
159    }
160}
161
162impl BackboneWriter {
163    fn write_buffer(
164        &mut self,
165        data: &[u8],
166        write_stall_timeout: Option<Duration>,
167    ) -> io::Result<()> {
168        let mut written = 0usize;
169        while written < data.len() {
170            match self.stream.write(&data[written..]) {
171                Ok(0) => {
172                    return Err(io::Error::new(
173                        io::ErrorKind::WriteZero,
174                        "backbone writer wrote zero bytes",
175                    ))
176                }
177                Ok(n) => {
178                    written += n;
179                    self.stall_started = None;
180                }
181                Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
182                    let now = Instant::now();
183                    let started = self.stall_started.get_or_insert(now);
184                    if let Some(timeout) = write_stall_timeout {
185                        if now.duration_since(*started) >= timeout {
186                            return Err(self.disconnect_for_write_stall(timeout));
187                        }
188                    }
189                    if self.pending.len() + data[written..].len() > MAX_PENDING_BYTES {
190                        return Err(self.disconnect_for_write_stall(
191                            write_stall_timeout.unwrap_or(Duration::from_secs(30)),
192                        ));
193                    }
194                    self.pending.extend_from_slice(&data[written..]);
195                    return Err(io::Error::new(
196                        io::ErrorKind::WouldBlock,
197                        "backbone writer would block",
198                    ));
199                }
200                Err(e) => return Err(e),
201            }
202        }
203        Ok(())
204    }
205
206    fn flush_pending(&mut self, write_stall_timeout: Option<Duration>) -> io::Result<()> {
207        if self.pending.is_empty() {
208            return Ok(());
209        }
210
211        let pending = std::mem::take(&mut self.pending);
212        match self.write_buffer(&pending, write_stall_timeout) {
213            Ok(()) => Ok(()),
214            Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(()),
215            Err(e) => Err(e),
216        }
217    }
218
219    fn disconnect_for_write_stall(&mut self, timeout: Duration) -> io::Error {
220        if !self.disconnect_notified {
221            log::warn!(
222                "[{}] backbone client {} disconnected due to write stall timeout ({:?})",
223                self.interface_name,
224                self.interface_id.0,
225                timeout
226            );
227            self.write_stall_flag.store(true, Ordering::Relaxed);
228            let _ = self.stream.shutdown(Shutdown::Both);
229            let _ = self.event_tx.send(Event::InterfaceDown(self.interface_id));
230            self.disconnect_notified = true;
231        }
232        io::Error::new(
233            io::ErrorKind::TimedOut,
234            format!("backbone writer stalled for {:?}", timeout),
235        )
236    }
237}
238
239/// Start a backbone interface. Binds TCP listener, spawns poll thread.
240pub fn start(config: BackboneConfig, tx: EventSender, next_id: Arc<AtomicU64>) -> io::Result<()> {
241    let addr = format!("{}:{}", config.listen_ip, config.listen_port);
242    let listener = TcpListener::bind(&addr)?;
243    listener.set_nonblocking(true)?;
244
245    log::info!(
246        "[{}] backbone server listening on {}",
247        config.name,
248        listener
249            .local_addr()
250            .unwrap_or_else(|_| std::net::SocketAddr::from(([0, 0, 0, 0], config.listen_port)))
251    );
252
253    let name = config.name.clone();
254    let server_interface_id = config.interface_id;
255    let runtime = Arc::clone(&config.runtime);
256    let peer_state = Arc::clone(&config.peer_state);
257    let ingress_control = config.ingress_control;
258    let accepted_peer_mode = config.mode;
259    let accepted_peer_recursive_prs = config.recursive_prs;
260    let accepted_peer_announces_from_internal = config.announces_from_internal;
261    thread::Builder::new()
262        .name(format!("backbone-poll-{}", config.interface_id.0))
263        .spawn(move || {
264            if let Err(e) = poll_loop(
265                listener,
266                name,
267                server_interface_id,
268                tx,
269                next_id,
270                runtime,
271                peer_state,
272                ingress_control,
273                accepted_peer_mode,
274                accepted_peer_recursive_prs,
275                accepted_peer_announces_from_internal,
276            ) {
277                log::error!("backbone poll loop error: {}", e);
278            }
279        })?;
280
281    Ok(())
282}
283
284/// Per-client tracking state.
285struct ClientState {
286    id: InterfaceId,
287    peer_ip: IpAddr,
288    peer_port: u16,
289    stream: TcpStream,
290    decoder: hdlc::Decoder,
291    connected_at: Instant,
292    has_received_data: bool,
293    write_stall_flag: Arc<AtomicBool>,
294}
295
296#[derive(Debug, Clone)]
297struct PeerBehaviorState {
298    blacklisted_until: Option<Instant>,
299    blacklist_reason: Option<String>,
300    reject_count: u64,
301    connected_count: usize,
302}
303
304impl PeerBehaviorState {
305    fn new() -> Self {
306        Self {
307            blacklisted_until: None,
308            blacklist_reason: None,
309            reject_count: 0,
310            connected_count: 0,
311        }
312    }
313}
314
315#[derive(Debug, Clone, Default)]
316pub struct BackbonePeerMonitor {
317    peers: HashMap<IpAddr, PeerBehaviorState>,
318}
319
320impl BackbonePeerMonitor {
321    pub fn new() -> Self {
322        Self {
323            peers: HashMap::new(),
324        }
325    }
326
327    fn upsert_snapshot(&mut self, peers: &HashMap<IpAddr, PeerBehaviorState>) {
328        let mut merged = self.peers.clone();
329
330        for (peer_ip, state) in peers {
331            let entry = merged
332                .entry(*peer_ip)
333                .or_insert_with(PeerBehaviorState::new);
334            entry.connected_count = state.connected_count;
335            entry.reject_count = state.reject_count;
336            if state.blacklisted_until.is_some() {
337                entry.blacklisted_until = state.blacklisted_until;
338                entry.blacklist_reason = state.blacklist_reason.clone();
339            }
340        }
341
342        merged.retain(|peer_ip, state| {
343            peers.contains_key(peer_ip)
344                || state.blacklisted_until.is_some()
345                || state.reject_count > 0
346        });
347        self.peers = merged;
348    }
349
350    fn sync_into(&self, peers: &mut HashMap<IpAddr, PeerBehaviorState>) {
351        for (peer_ip, state) in &self.peers {
352            let entry = peers.entry(*peer_ip).or_insert_with(PeerBehaviorState::new);
353            entry.blacklisted_until = state.blacklisted_until;
354            entry.blacklist_reason = state.blacklist_reason.clone();
355            entry.reject_count = state.reject_count;
356        }
357
358        peers.retain(|peer_ip, state| {
359            if state.connected_count > 0 {
360                return true;
361            }
362            self.peers.contains_key(peer_ip)
363        });
364    }
365
366    pub fn list(&self, interface_name: &str) -> Vec<BackbonePeerStateEntry> {
367        let now = Instant::now();
368        let mut entries: Vec<BackbonePeerStateEntry> = self
369            .peers
370            .iter()
371            .map(|(peer_ip, state)| BackbonePeerStateEntry {
372                interface_name: interface_name.to_string(),
373                peer_ip: *peer_ip,
374                connected_count: state.connected_count,
375                blacklisted_remaining_secs: state
376                    .blacklisted_until
377                    .and_then(|until| (until > now).then(|| (until - now).as_secs_f64())),
378                blacklist_reason: state.blacklist_reason.clone(),
379                reject_count: state.reject_count,
380            })
381            .collect();
382        entries.sort_by(|a, b| a.peer_ip.cmp(&b.peer_ip));
383        entries
384    }
385
386    pub fn clear(&mut self, peer_ip: IpAddr) -> bool {
387        self.peers.remove(&peer_ip).is_some()
388    }
389
390    pub fn blacklist(&mut self, peer_ip: IpAddr, duration: Duration, reason: String) -> bool {
391        let state = self
392            .peers
393            .entry(peer_ip)
394            .or_insert_with(PeerBehaviorState::new);
395        state.blacklisted_until = Some(Instant::now() + duration);
396        state.blacklist_reason = Some(reason);
397        true
398    }
399
400    #[cfg(test)]
401    pub fn seed_entry(&mut self, entry: BackbonePeerStateEntry) {
402        let mut state = PeerBehaviorState::new();
403        state.connected_count = entry.connected_count;
404        state.reject_count = entry.reject_count;
405        state.blacklist_reason = entry.blacklist_reason;
406        if let Some(remaining) = entry.blacklisted_remaining_secs {
407            state.blacklisted_until = Some(Instant::now() + Duration::from_secs_f64(remaining));
408        }
409        self.peers.insert(entry.peer_ip, state);
410    }
411}
412
413#[derive(Clone, Copy)]
414enum DisconnectReason {
415    RemoteClosed,
416    IdleTimeout,
417    WriteStall,
418}
419
420/// Main poll event loop.
421fn poll_loop(
422    listener: TcpListener,
423    name: String,
424    server_interface_id: InterfaceId,
425    tx: EventSender,
426    next_id: Arc<AtomicU64>,
427    runtime: Arc<Mutex<BackboneServerRuntime>>,
428    peer_state: Arc<Mutex<BackbonePeerMonitor>>,
429    ingress_control: IngressControlConfig,
430    accepted_peer_mode: u8,
431    accepted_peer_recursive_prs: bool,
432    accepted_peer_announces_from_internal: bool,
433) -> io::Result<()> {
434    let poller = Poller::new()?;
435
436    const LISTENER_KEY: usize = 0;
437
438    // SAFETY: listener outlives its registration in the poller.
439    unsafe { poller.add(&listener, PollEvent::readable(LISTENER_KEY))? };
440
441    let mut clients: HashMap<usize, ClientState> = HashMap::new();
442    let mut peers: HashMap<IpAddr, PeerBehaviorState> = HashMap::new();
443    let mut events = Events::new();
444    let mut next_key: usize = 1;
445
446    loop {
447        let runtime_snapshot = runtime.lock().unwrap().clone();
448        let max_connections = runtime_snapshot.max_connections;
449        let idle_timeout = runtime_snapshot.idle_timeout;
450        cleanup_peer_state(&mut peers);
451        {
452            let mut monitor = peer_state.lock().unwrap();
453            monitor.sync_into(&mut peers);
454            monitor.upsert_snapshot(&peers);
455        }
456
457        events.clear();
458        poller.wait(&mut events, Some(Duration::from_secs(1)))?;
459
460        for ev in events.iter() {
461            if ev.key == LISTENER_KEY {
462                // Accept new connections
463                loop {
464                    match listener.accept() {
465                        Ok((stream, peer_addr)) => {
466                            let peer_ip = peer_addr.ip();
467                            let peer_port = peer_addr.port();
468
469                            if is_ip_blacklisted(&mut peers, peer_ip) {
470                                if let Some(state) = peers.get_mut(&peer_ip) {
471                                    state.reject_count = state.reject_count.saturating_add(1);
472                                }
473                                peer_state.lock().unwrap().upsert_snapshot(&peers);
474                                log::debug!("[{}] rejecting blacklisted peer {}", name, peer_addr);
475                                drop(stream);
476                                continue;
477                            }
478
479                            if let Some(max) = max_connections {
480                                if clients.len() >= max {
481                                    log::warn!(
482                                        "[{}] max connections ({}) reached, rejecting {}",
483                                        name,
484                                        max,
485                                        peer_addr
486                                    );
487                                    drop(stream);
488                                    continue;
489                                }
490                            }
491
492                            stream.set_nonblocking(true).ok();
493                            stream.set_nodelay(true).ok();
494                            set_tcp_keepalive(&stream).ok();
495
496                            // Prevent SIGPIPE on macOS when writing to broken pipes
497                            #[cfg(target_os = "macos")]
498                            {
499                                let sock = SockRef::from(&stream);
500                                sock.set_nosigpipe(true).ok();
501                            }
502
503                            let key = next_key;
504                            next_key += 1;
505                            let client_id = InterfaceId(next_id.fetch_add(1, Ordering::Relaxed));
506
507                            log::info!(
508                                "[{}] backbone client connected: {} → id {}",
509                                name,
510                                peer_addr,
511                                client_id.0
512                            );
513
514                            // Register client with poller
515                            // SAFETY: stream is stored in ClientState and outlives registration.
516                            if let Err(e) = unsafe { poller.add(&stream, PollEvent::readable(key)) }
517                            {
518                                log::warn!("[{}] failed to add client to poller: {}", name, e);
519                                continue; // stream drops, closing socket
520                            }
521
522                            // Create writer via try_clone (cross-platform dup)
523                            let writer_stream = match stream.try_clone() {
524                                Ok(s) => s,
525                                Err(e) => {
526                                    log::warn!("[{}] failed to clone client stream: {}", name, e);
527                                    let _ = poller.delete(&stream);
528                                    continue; // stream drops
529                                }
530                            };
531                            let write_stall_flag = Arc::new(AtomicBool::new(false));
532                            let writer: Box<dyn Writer> = Box::new(BackboneWriter {
533                                stream: writer_stream,
534                                runtime: Arc::clone(&runtime),
535                                interface_name: name.clone(),
536                                interface_id: client_id,
537                                event_tx: tx.clone(),
538                                pending: Vec::new(),
539                                stall_started: None,
540                                disconnect_notified: false,
541                                write_stall_flag: Arc::clone(&write_stall_flag),
542                            });
543
544                            clients.insert(
545                                key,
546                                ClientState {
547                                    id: client_id,
548                                    peer_ip,
549                                    peer_port,
550                                    stream,
551                                    decoder: hdlc::Decoder::new(),
552                                    connected_at: Instant::now(),
553                                    has_received_data: false,
554                                    write_stall_flag,
555                                },
556                            );
557                            peers
558                                .entry(peer_ip)
559                                .or_insert_with(PeerBehaviorState::new)
560                                .connected_count += 1;
561                            peer_state.lock().unwrap().upsert_snapshot(&peers);
562                            let _ = tx.send(Event::BackbonePeerConnected {
563                                server_interface_id,
564                                peer_interface_id: client_id,
565                                peer_ip,
566                                peer_port,
567                            });
568
569                            let info = InterfaceInfo {
570                                id: client_id,
571                                name: format!("BackboneInterface/{}", client_id.0),
572                                mode: accepted_peer_mode,
573                                recursive_prs: accepted_peer_recursive_prs,
574                                announces_from_internal: accepted_peer_announces_from_internal,
575                                out_capable: true,
576                                in_capable: true,
577                                bitrate: Some(1_000_000_000), // 1 Gbps guess
578                                airtime_profile: None,
579                                announce_rate_target: None,
580                                announce_rate_grace: 0,
581                                announce_rate_penalty: 0.0,
582                                announce_cap: constants::ANNOUNCE_CAP,
583                                is_local_client: false,
584                                wants_tunnel: false,
585                                tunnel_id: None,
586                                mtu: 65535,
587                                ia_freq: 0.0,
588                                ip_freq: 0.0,
589                                op_freq: 0.0,
590                                op_samples: 0,
591                                started: 0.0,
592                                ingress_control,
593                            };
594
595                            if tx
596                                .send(Event::InterfaceUp(client_id, Some(writer), Some(info)))
597                                .is_err()
598                            {
599                                // Driver shut down
600                                cleanup(&poller, &clients, &listener);
601                                return Ok(());
602                            }
603                        }
604                        Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
605                        Err(e) => {
606                            log::warn!("[{}] accept error: {}", name, e);
607                            break;
608                        }
609                    }
610                }
611                // Re-arm listener (oneshot semantics)
612                poller.modify(&listener, PollEvent::readable(LISTENER_KEY))?;
613            } else if clients.contains_key(&ev.key) {
614                let key = ev.key;
615                let mut should_remove = false;
616                let mut client_id = InterfaceId(0);
617
618                let mut buf = [0u8; 4096];
619                let read_result = {
620                    let client = clients.get_mut(&key).unwrap();
621                    client.stream.read(&mut buf)
622                };
623
624                match read_result {
625                    Ok(0) | Err(_) => {
626                        if let Some(c) = clients.get(&key) {
627                            client_id = c.id;
628                        }
629                        should_remove = true;
630                    }
631                    Ok(n) => {
632                        let client = clients.get_mut(&key).unwrap();
633                        client_id = client.id;
634                        client.has_received_data = true;
635                        for frame in client.decoder.feed(&buf[..n]) {
636                            if tx
637                                .send(Event::Frame {
638                                    interface_id: client_id,
639                                    data: frame,
640                                    rssi: None,
641                                    snr: None,
642                                })
643                                .is_err()
644                            {
645                                cleanup(&poller, &clients, &listener);
646                                return Ok(());
647                            }
648                        }
649                    }
650                }
651
652                if should_remove {
653                    let reason = if clients
654                        .get(&key)
655                        .is_some_and(|c| c.write_stall_flag.load(Ordering::Relaxed))
656                    {
657                        DisconnectReason::WriteStall
658                    } else {
659                        DisconnectReason::RemoteClosed
660                    };
661                    disconnect_client(
662                        &poller,
663                        &mut clients,
664                        &mut peers,
665                        &name,
666                        server_interface_id,
667                        &tx,
668                        &peer_state,
669                        key,
670                        client_id,
671                        reason,
672                    );
673                } else if let Some(client) = clients.get(&key) {
674                    // Re-arm client (oneshot semantics)
675                    poller.modify(&client.stream, PollEvent::readable(key))?;
676                }
677            }
678        }
679
680        if let Some(timeout) = idle_timeout {
681            let now = Instant::now();
682            let timed_out: Vec<(usize, InterfaceId)> = clients
683                .iter()
684                .filter_map(|(&key, client)| {
685                    if client.has_received_data || now.duration_since(client.connected_at) < timeout
686                    {
687                        None
688                    } else {
689                        Some((key, client.id))
690                    }
691                })
692                .collect();
693
694            for (key, client_id) in timed_out {
695                disconnect_client(
696                    &poller,
697                    &mut clients,
698                    &mut peers,
699                    &name,
700                    server_interface_id,
701                    &tx,
702                    &peer_state,
703                    key,
704                    client_id,
705                    DisconnectReason::IdleTimeout,
706                );
707            }
708        }
709    }
710}
711
712fn cleanup_peer_state(peers: &mut HashMap<IpAddr, PeerBehaviorState>) {
713    let now = Instant::now();
714    peers.retain(|_, state| {
715        if matches!(state.blacklisted_until, Some(until) if now >= until) {
716            state.blacklisted_until = None;
717            state.blacklist_reason = None;
718        }
719        state.blacklisted_until.is_some() || state.connected_count > 0 || state.reject_count > 0
720    });
721}
722
723fn is_ip_blacklisted(peers: &mut HashMap<IpAddr, PeerBehaviorState>, peer_ip: IpAddr) -> bool {
724    let now = Instant::now();
725    if let Some(state) = peers.get_mut(&peer_ip) {
726        if let Some(until) = state.blacklisted_until {
727            if now < until {
728                return true;
729            }
730            state.blacklisted_until = None;
731        }
732    }
733    false
734}
735
736fn disconnect_client(
737    poller: &Poller,
738    clients: &mut HashMap<usize, ClientState>,
739    peers: &mut HashMap<IpAddr, PeerBehaviorState>,
740    name: &str,
741    server_interface_id: InterfaceId,
742    tx: &EventSender,
743    peer_state: &Arc<Mutex<BackbonePeerMonitor>>,
744    key: usize,
745    client_id: InterfaceId,
746    reason: DisconnectReason,
747) {
748    let Some(client) = clients.remove(&key) else {
749        return;
750    };
751
752    match reason {
753        DisconnectReason::RemoteClosed => {
754            log::info!("[{}] backbone client {} disconnected", name, client_id.0);
755        }
756        DisconnectReason::IdleTimeout => {
757            log::info!(
758                "[{}] backbone client {} disconnected due to idle timeout",
759                name,
760                client_id.0
761            );
762        }
763        DisconnectReason::WriteStall => {
764            // Already logged by BackboneWriter::disconnect_for_write_stall
765        }
766    }
767
768    let _ = poller.delete(&client.stream);
769    // client.stream closes on drop
770    let connected_for = client.connected_at.elapsed();
771    let _ = tx.send(Event::BackbonePeerDisconnected {
772        server_interface_id,
773        peer_interface_id: client.id,
774        peer_ip: client.peer_ip,
775        peer_port: client.peer_port,
776        connected_for,
777        had_received_data: client.has_received_data,
778    });
779    match reason {
780        DisconnectReason::IdleTimeout => {
781            let _ = tx.send(Event::BackbonePeerIdleTimeout {
782                server_interface_id,
783                peer_interface_id: client.id,
784                peer_ip: client.peer_ip,
785                peer_port: client.peer_port,
786                connected_for,
787            });
788        }
789        DisconnectReason::WriteStall => {
790            let _ = tx.send(Event::BackbonePeerWriteStall {
791                server_interface_id,
792                peer_interface_id: client.id,
793                peer_ip: client.peer_ip,
794                peer_port: client.peer_port,
795                connected_for,
796            });
797        }
798        DisconnectReason::RemoteClosed => {}
799    }
800
801    if let Some(state) = peers.get_mut(&client.peer_ip) {
802        state.connected_count = state.connected_count.saturating_sub(1);
803    }
804    peer_state.lock().unwrap().upsert_snapshot(peers);
805    // Writer already sent InterfaceDown for write stalls; avoid duplicate.
806    if !matches!(reason, DisconnectReason::WriteStall) {
807        let _ = tx.send(Event::InterfaceDown(client_id));
808    }
809}
810
811fn set_tcp_keepalive(stream: &TcpStream) -> io::Result<()> {
812    let sock = SockRef::from(stream);
813    let mut keepalive = TcpKeepalive::new()
814        .with_time(Duration::from_secs(5))
815        .with_interval(Duration::from_secs(2));
816    #[cfg(any(target_os = "linux", target_os = "macos"))]
817    {
818        keepalive = keepalive.with_retries(12);
819    }
820    sock.set_tcp_keepalive(&keepalive)
821}
822
823fn cleanup(poller: &Poller, clients: &HashMap<usize, ClientState>, listener: &TcpListener) {
824    for (_, client) in clients {
825        let _ = poller.delete(&client.stream);
826    }
827    let _ = poller.delete(listener);
828}
829
830// ---------------------------------------------------------------------------
831// Client mode
832// ---------------------------------------------------------------------------
833
834/// Configuration for a backbone client interface.
835#[derive(Debug, Clone)]
836pub struct BackboneClientConfig {
837    pub name: String,
838    pub target_host: String,
839    pub target_port: u16,
840    pub interface_id: InterfaceId,
841    pub reconnect_wait: Duration,
842    pub max_reconnect_tries: Option<u32>,
843    pub connect_timeout: Duration,
844    pub transport_identity: Option<String>,
845    pub runtime: Arc<Mutex<BackboneClientRuntime>>,
846}
847
848#[derive(Debug, Clone)]
849pub struct BackboneClientRuntime {
850    pub reconnect_wait: Duration,
851    pub max_reconnect_tries: Option<u32>,
852    pub connect_timeout: Duration,
853}
854
855impl BackboneClientRuntime {
856    pub fn from_config(config: &BackboneClientConfig) -> Self {
857        Self {
858            reconnect_wait: config.reconnect_wait,
859            max_reconnect_tries: config.max_reconnect_tries,
860            connect_timeout: config.connect_timeout,
861        }
862    }
863}
864
865#[derive(Debug, Clone)]
866pub struct BackboneClientRuntimeConfigHandle {
867    pub interface_name: String,
868    pub runtime: Arc<Mutex<BackboneClientRuntime>>,
869    pub startup: BackboneClientRuntime,
870}
871
872impl Default for BackboneClientConfig {
873    fn default() -> Self {
874        let mut config = BackboneClientConfig {
875            name: String::new(),
876            target_host: "127.0.0.1".into(),
877            target_port: 4242,
878            interface_id: InterfaceId(0),
879            reconnect_wait: Duration::from_secs(5),
880            max_reconnect_tries: None,
881            connect_timeout: Duration::from_secs(5),
882            transport_identity: None,
883            runtime: Arc::new(Mutex::new(BackboneClientRuntime {
884                reconnect_wait: Duration::from_secs(5),
885                max_reconnect_tries: None,
886                connect_timeout: Duration::from_secs(5),
887            })),
888        };
889        let startup = BackboneClientRuntime::from_config(&config);
890        config.runtime = Arc::new(Mutex::new(startup));
891        config
892    }
893}
894
895/// Writer that sends HDLC-framed data over a TCP stream (client mode).
896struct BackboneClientWriter {
897    stream: TcpStream,
898}
899
900impl Writer for BackboneClientWriter {
901    fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
902        self.stream.write_all(&hdlc::frame(data))
903    }
904}
905
906/// Try to connect to the target host:port with timeout.
907fn try_connect_client(config: &BackboneClientConfig) -> io::Result<TcpStream> {
908    let runtime = config.runtime.lock().unwrap().clone();
909    let addr_str = format!("{}:{}", config.target_host, config.target_port);
910    let addr = addr_str
911        .to_socket_addrs()?
912        .next()
913        .ok_or_else(|| io::Error::new(io::ErrorKind::AddrNotAvailable, "no addresses resolved"))?;
914
915    let stream = TcpStream::connect_timeout(&addr, runtime.connect_timeout)?;
916    stream.set_nodelay(true)?;
917    set_tcp_keepalive(&stream).ok();
918
919    // Prevent SIGPIPE on macOS when writing to broken pipes
920    #[cfg(target_os = "macos")]
921    {
922        let sock = SockRef::from(&stream);
923        sock.set_nosigpipe(true).ok();
924    }
925
926    Ok(stream)
927}
928
929/// Connect and start the reader thread. Returns the writer for the driver.
930pub fn start_client(config: BackboneClientConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
931    let stream = try_connect_client(&config)?;
932    let reader_stream = stream.try_clone()?;
933    let writer_stream = stream.try_clone()?;
934
935    let id = config.interface_id;
936    log::info!(
937        "[{}] backbone client connected to {}:{}",
938        config.name,
939        config.target_host,
940        config.target_port
941    );
942
943    // Initial connect: writer is None because it's returned directly to the caller
944    let _ = tx.send(Event::InterfaceUp(id, None, None));
945
946    thread::Builder::new()
947        .name(format!("backbone-client-{}", id.0))
948        .spawn(move || {
949            client_reader_loop(reader_stream, config, tx);
950        })?;
951
952    Ok(Box::new(BackboneClientWriter {
953        stream: writer_stream,
954    }))
955}
956
957/// Reader thread: reads from socket, HDLC-decodes, sends frames to driver.
958/// On disconnect, attempts reconnection.
959fn client_reader_loop(mut stream: TcpStream, config: BackboneClientConfig, tx: EventSender) {
960    let id = config.interface_id;
961    let mut decoder = hdlc::Decoder::new();
962    let mut buf = [0u8; 4096];
963
964    loop {
965        match stream.read(&mut buf) {
966            Ok(0) => {
967                log::warn!("[{}] connection closed", config.name);
968                let _ = tx.send(Event::InterfaceDown(id));
969                match client_reconnect(&config, &tx) {
970                    Some(new_stream) => {
971                        stream = new_stream;
972                        decoder = hdlc::Decoder::new();
973                        continue;
974                    }
975                    None => {
976                        log::error!("[{}] reconnection failed, giving up", config.name);
977                        return;
978                    }
979                }
980            }
981            Ok(n) => {
982                for frame in decoder.feed(&buf[..n]) {
983                    if tx
984                        .send(Event::Frame {
985                            interface_id: id,
986                            data: frame,
987                            rssi: None,
988                            snr: None,
989                        })
990                        .is_err()
991                    {
992                        return;
993                    }
994                }
995            }
996            Err(e) => {
997                log::warn!("[{}] read error: {}", config.name, e);
998                let _ = tx.send(Event::InterfaceDown(id));
999                match client_reconnect(&config, &tx) {
1000                    Some(new_stream) => {
1001                        stream = new_stream;
1002                        decoder = hdlc::Decoder::new();
1003                        continue;
1004                    }
1005                    None => {
1006                        log::error!("[{}] reconnection failed, giving up", config.name);
1007                        return;
1008                    }
1009                }
1010            }
1011        }
1012    }
1013}
1014
1015/// Maximum backoff multiplier: `base_delay * 2^MAX_BACKOFF_SHIFT`.
1016/// With a 5 s base this caps at 5 × 2^6 = 320 s ≈ 5 min.
1017const MAX_BACKOFF_SHIFT: u32 = 6;
1018
1019/// Attempt to reconnect with exponential backoff and jitter.
1020/// Returns the new reader stream on success.
1021/// Sends the new writer to the driver via InterfaceUp event.
1022fn client_reconnect(config: &BackboneClientConfig, tx: &EventSender) -> Option<TcpStream> {
1023    let mut attempts = 0u32;
1024    loop {
1025        let runtime = config.runtime.lock().unwrap().clone();
1026
1027        let shift = attempts.min(MAX_BACKOFF_SHIFT);
1028        let backoff = runtime.reconnect_wait * 2u32.pow(shift);
1029        // Add ±25 % jitter to avoid thundering-herd reconnects.
1030        let jitter_range = backoff / 4;
1031        let jitter = if jitter_range.as_nanos() > 0 {
1032            let offset = Duration::from_nanos(
1033                (std::hash::RandomState::new().build_hasher().finish()
1034                    % jitter_range.as_nanos() as u64)
1035                    * 2,
1036            );
1037            if offset > jitter_range {
1038                backoff + (offset - jitter_range)
1039            } else {
1040                backoff - (jitter_range - offset)
1041            }
1042        } else {
1043            backoff
1044        };
1045        thread::sleep(jitter);
1046
1047        attempts += 1;
1048
1049        if let Some(max) = runtime.max_reconnect_tries {
1050            if attempts > max {
1051                let _ = tx.send(Event::InterfaceDown(config.interface_id));
1052                return None;
1053            }
1054        }
1055
1056        log::info!(
1057            "[{}] reconnect attempt {} (backoff {:.1}s) ...",
1058            config.name,
1059            attempts,
1060            jitter.as_secs_f64(),
1061        );
1062
1063        match try_connect_client(config) {
1064            Ok(new_stream) => {
1065                let writer_stream = match new_stream.try_clone() {
1066                    Ok(s) => s,
1067                    Err(e) => {
1068                        log::warn!("[{}] failed to clone stream: {}", config.name, e);
1069                        continue;
1070                    }
1071                };
1072                log::info!(
1073                    "[{}] reconnected after {} attempt(s)",
1074                    config.name,
1075                    attempts
1076                );
1077                let new_writer: Box<dyn Writer> = Box::new(BackboneClientWriter {
1078                    stream: writer_stream,
1079                });
1080                let _ = tx.send(Event::InterfaceUp(
1081                    config.interface_id,
1082                    Some(new_writer),
1083                    None,
1084                ));
1085                return Some(new_stream);
1086            }
1087            Err(e) => {
1088                log::warn!("[{}] reconnect failed: {}", config.name, e);
1089            }
1090        }
1091    }
1092}
1093
1094// ---------------------------------------------------------------------------
1095// Factory
1096// ---------------------------------------------------------------------------
1097
1098/// Internal enum used by [`BackboneInterfaceFactory`] to carry either a
1099/// server or client config through the opaque `InterfaceConfigData` channel.
1100#[derive(Clone)]
1101pub(crate) enum BackboneMode {
1102    Server(BackboneConfig),
1103    Client(BackboneClientConfig, u8),
1104}
1105
1106/// Factory for `BackboneInterface`.
1107///
1108/// If the config params contain `"remote"` or `"target_host"` the interface
1109/// is started in client mode; otherwise it is started as a TCP listener
1110/// (server mode).
1111pub struct BackboneInterfaceFactory;
1112
1113fn parse_positive_duration_secs(params: &HashMap<String, String>, key: &str) -> Option<Duration> {
1114    params
1115        .get(key)
1116        .and_then(|v| v.parse::<f64>().ok())
1117        .filter(|v| *v > 0.0)
1118        .map(Duration::from_secs_f64)
1119}
1120
1121impl InterfaceFactory for BackboneInterfaceFactory {
1122    fn type_name(&self) -> &str {
1123        "BackboneInterface"
1124    }
1125
1126    fn parse_config(
1127        &self,
1128        name: &str,
1129        id: InterfaceId,
1130        params: &HashMap<String, String>,
1131    ) -> Result<Box<dyn InterfaceConfigData>, String> {
1132        if let Some(target_host) = params.get("remote").or_else(|| params.get("target_host")) {
1133            // Client mode
1134            let target_host = target_host.clone();
1135            let target_port = params
1136                .get("target_port")
1137                .or_else(|| params.get("port"))
1138                .and_then(|v| v.parse().ok())
1139                .unwrap_or(4242);
1140            let transport_identity = params.get("transport_identity").cloned();
1141            let priority = match params.get("priority") {
1142                Some(value) => value.parse::<u8>().map_err(|_| {
1143                    format!(
1144                        "invalid Backbone peer priority '{}' (expected 0..100)",
1145                        value
1146                    )
1147                })?,
1148                None => crate::driver::BACKBONE_PEER_POOL_CONFIGURED_DEFAULT_PRIORITY,
1149            };
1150            if priority > 100 {
1151                return Err(format!(
1152                    "invalid Backbone peer priority '{}' (expected 0..100)",
1153                    priority
1154                ));
1155            }
1156            Ok(Box::new(BackboneMode::Client(
1157                BackboneClientConfig {
1158                    name: name.to_string(),
1159                    target_host,
1160                    target_port,
1161                    interface_id: id,
1162                    transport_identity,
1163                    ..BackboneClientConfig::default()
1164                },
1165                priority,
1166            )))
1167        } else {
1168            // Server mode
1169            let listen_ip = params
1170                .get("listen_ip")
1171                .or_else(|| params.get("device"))
1172                .cloned()
1173                .unwrap_or_else(|| "0.0.0.0".into());
1174            let listen_port = params
1175                .get("listen_port")
1176                .or_else(|| params.get("port"))
1177                .and_then(|v| v.parse().ok())
1178                .unwrap_or(4242);
1179            let max_connections = params.get("max_connections").and_then(|v| v.parse().ok());
1180            let idle_timeout = parse_positive_duration_secs(params, "idle_timeout");
1181            let write_stall_timeout = parse_positive_duration_secs(params, "write_stall_timeout");
1182            let abuse = BackboneAbuseConfig {
1183                max_penalty_duration: parse_positive_duration_secs(params, "max_penalty_duration"),
1184            };
1185            let mut config = BackboneConfig {
1186                name: name.to_string(),
1187                listen_ip,
1188                listen_port,
1189                interface_id: id,
1190                mode: constants::MODE_FULL,
1191                recursive_prs: false,
1192                announces_from_internal: true,
1193                max_connections,
1194                idle_timeout,
1195                write_stall_timeout,
1196                abuse,
1197                ingress_control: IngressControlConfig::enabled(),
1198                runtime: Arc::new(Mutex::new(BackboneServerRuntime {
1199                    max_connections: None,
1200                    idle_timeout: None,
1201                    write_stall_timeout: None,
1202                    abuse: BackboneAbuseConfig::default(),
1203                })),
1204                peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
1205            };
1206            let startup = BackboneServerRuntime::from_config(&config);
1207            config.runtime = Arc::new(Mutex::new(startup));
1208            Ok(Box::new(BackboneMode::Server(config)))
1209        }
1210    }
1211
1212    fn start(
1213        &self,
1214        config: Box<dyn InterfaceConfigData>,
1215        ctx: StartContext,
1216    ) -> io::Result<StartResult> {
1217        let mode = *config.into_any().downcast::<BackboneMode>().map_err(|_| {
1218            io::Error::new(
1219                io::ErrorKind::InvalidData,
1220                "wrong config type for BackboneInterface",
1221            )
1222        })?;
1223
1224        match mode {
1225            BackboneMode::Client(cfg, _) => {
1226                let id = cfg.interface_id;
1227                let name = cfg.name.clone();
1228                let info = InterfaceInfo {
1229                    id,
1230                    name,
1231                    mode: ctx.mode,
1232                    recursive_prs: ctx.recursive_prs,
1233                    announces_from_internal: ctx.announces_from_internal,
1234                    out_capable: true,
1235                    in_capable: true,
1236                    bitrate: Some(1_000_000_000),
1237                    airtime_profile: None,
1238                    announce_rate_target: None,
1239                    announce_rate_grace: 0,
1240                    announce_rate_penalty: 0.0,
1241                    announce_cap: constants::ANNOUNCE_CAP,
1242                    is_local_client: false,
1243                    wants_tunnel: false,
1244                    tunnel_id: None,
1245                    mtu: 65535,
1246                    ingress_control: ctx.ingress_control,
1247                    ia_freq: 0.0,
1248                    ip_freq: 0.0,
1249                    op_freq: 0.0,
1250                    op_samples: 0,
1251                    started: crate::time::now(),
1252                };
1253                let writer = start_client(cfg, ctx.tx)?;
1254                Ok(StartResult::Simple {
1255                    id,
1256                    info,
1257                    writer,
1258                    interface_type_name: "BackboneInterface".to_string(),
1259                })
1260            }
1261            BackboneMode::Server(mut cfg) => {
1262                cfg.ingress_control = ctx.ingress_control;
1263                cfg.mode = ctx.mode;
1264                cfg.recursive_prs = ctx.recursive_prs;
1265                start(cfg, ctx.tx, ctx.next_dynamic_id)?;
1266                Ok(StartResult::Listener { control: None })
1267            }
1268        }
1269    }
1270}
1271
1272pub(crate) fn runtime_handle_from_mode(mode: &BackboneMode) -> Option<BackboneRuntimeConfigHandle> {
1273    match mode {
1274        BackboneMode::Server(config) => Some(BackboneRuntimeConfigHandle {
1275            interface_name: config.name.clone(),
1276            runtime: Arc::clone(&config.runtime),
1277            startup: BackboneServerRuntime::from_config(config),
1278        }),
1279        BackboneMode::Client(_, _) => None,
1280    }
1281}
1282
1283pub(crate) fn peer_state_handle_from_mode(mode: &BackboneMode) -> Option<BackbonePeerStateHandle> {
1284    match mode {
1285        BackboneMode::Server(config) => Some(BackbonePeerStateHandle {
1286            interface_id: config.interface_id,
1287            interface_name: config.name.clone(),
1288            peer_state: Arc::clone(&config.peer_state),
1289        }),
1290        BackboneMode::Client(_, _) => None,
1291    }
1292}
1293
1294pub(crate) fn client_runtime_handle_from_mode(
1295    mode: &BackboneMode,
1296) -> Option<BackboneClientRuntimeConfigHandle> {
1297    match mode {
1298        BackboneMode::Client(config, _) => Some(BackboneClientRuntimeConfigHandle {
1299            interface_name: config.name.clone(),
1300            runtime: Arc::clone(&config.runtime),
1301            startup: BackboneClientRuntime::from_config(config),
1302        }),
1303        BackboneMode::Server(_) => None,
1304    }
1305}
1306
1307pub(crate) fn client_config_from_mode(mode: &BackboneMode) -> Option<BackboneClientConfig> {
1308    match mode {
1309        BackboneMode::Client(config, _) => Some(config.clone()),
1310        BackboneMode::Server(_) => None,
1311    }
1312}
1313
1314pub(crate) fn client_priority_from_mode(mode: &BackboneMode) -> Option<u8> {
1315    match mode {
1316        BackboneMode::Client(_, priority) => Some(*priority),
1317        BackboneMode::Server(_) => None,
1318    }
1319}
1320
1321#[cfg(test)]
1322mod tests {
1323    use super::*;
1324    use std::sync::mpsc;
1325    use std::time::Duration;
1326
1327    fn find_free_port() -> u16 {
1328        TcpListener::bind("127.0.0.1:0")
1329            .unwrap()
1330            .local_addr()
1331            .unwrap()
1332            .port()
1333    }
1334
1335    fn recv_non_peer_event(
1336        rx: &mpsc::Receiver<Event>,
1337        timeout: Duration,
1338    ) -> Result<Event, mpsc::RecvTimeoutError> {
1339        let deadline = Instant::now() + timeout;
1340        loop {
1341            let remaining = deadline.saturating_duration_since(Instant::now());
1342            if remaining.is_zero() {
1343                return Err(mpsc::RecvTimeoutError::Timeout);
1344            }
1345            let event = rx.recv_timeout(remaining)?;
1346            match event {
1347                Event::BackbonePeerConnected { .. }
1348                | Event::BackbonePeerDisconnected { .. }
1349                | Event::BackbonePeerIdleTimeout { .. }
1350                | Event::BackbonePeerWriteStall { .. }
1351                | Event::BackbonePeerPenalty { .. } => continue,
1352                other => return Ok(other),
1353            }
1354        }
1355    }
1356
1357    fn make_server_config(
1358        port: u16,
1359        interface_id: u64,
1360        max_connections: Option<usize>,
1361        idle_timeout: Option<Duration>,
1362        write_stall_timeout: Option<Duration>,
1363        abuse: BackboneAbuseConfig,
1364    ) -> BackboneConfig {
1365        let mut config = BackboneConfig {
1366            name: "test-backbone".into(),
1367            listen_ip: "127.0.0.1".into(),
1368            listen_port: port,
1369            interface_id: InterfaceId(interface_id),
1370            mode: constants::MODE_FULL,
1371            recursive_prs: false,
1372            announces_from_internal: true,
1373            max_connections,
1374            idle_timeout,
1375            write_stall_timeout,
1376            abuse,
1377            ingress_control: IngressControlConfig::enabled(),
1378            runtime: Arc::new(Mutex::new(BackboneServerRuntime {
1379                max_connections: None,
1380                idle_timeout: None,
1381                write_stall_timeout: None,
1382                abuse: BackboneAbuseConfig::default(),
1383            })),
1384            peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
1385        };
1386        let startup = BackboneServerRuntime::from_config(&config);
1387        config.runtime = Arc::new(Mutex::new(startup));
1388        config
1389    }
1390
1391    #[test]
1392    fn backbone_accept_connection() {
1393        let port = find_free_port();
1394        let (tx, rx) = crate::event::channel();
1395        let next_id = Arc::new(AtomicU64::new(8000));
1396
1397        let config = make_server_config(port, 80, None, None, None, BackboneAbuseConfig::default());
1398
1399        start(config, tx, next_id).unwrap();
1400        thread::sleep(Duration::from_millis(50));
1401
1402        let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1403
1404        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1405        match event {
1406            Event::InterfaceUp(id, writer, info) => {
1407                assert_eq!(id, InterfaceId(8000));
1408                assert!(writer.is_some());
1409                assert!(info.is_some());
1410                let info = info.unwrap();
1411                assert!(info.out_capable);
1412                assert!(info.in_capable);
1413            }
1414            other => panic!("expected InterfaceUp, got {:?}", other),
1415        }
1416    }
1417
1418    #[test]
1419    fn backbone_accepted_connection_inherits_server_mode() {
1420        let port = find_free_port();
1421        let (tx, rx) = crate::event::channel();
1422        let next_id = Arc::new(AtomicU64::new(8050));
1423
1424        let mut config =
1425            make_server_config(port, 85, None, None, None, BackboneAbuseConfig::default());
1426        config.mode = constants::MODE_GATEWAY;
1427
1428        start(config, tx, next_id).unwrap();
1429        thread::sleep(Duration::from_millis(50));
1430
1431        let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1432
1433        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1434        match event {
1435            Event::InterfaceUp(_, _, Some(info)) => {
1436                assert_eq!(info.mode, constants::MODE_GATEWAY);
1437            }
1438            other => panic!("expected InterfaceUp with info, got {:?}", other),
1439        }
1440    }
1441
1442    #[test]
1443    fn backbone_receive_frame() {
1444        let port = find_free_port();
1445        let (tx, rx) = crate::event::channel();
1446        let next_id = Arc::new(AtomicU64::new(8100));
1447
1448        let config = make_server_config(port, 81, None, None, None, BackboneAbuseConfig::default());
1449
1450        start(config, tx, next_id).unwrap();
1451        thread::sleep(Duration::from_millis(50));
1452
1453        let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1454
1455        // Drain InterfaceUp
1456        let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1457
1458        // Send HDLC frame (>= 19 bytes)
1459        let payload: Vec<u8> = (0..32).collect();
1460        client.write_all(&hdlc::frame(&payload)).unwrap();
1461
1462        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1463        match event {
1464            Event::Frame {
1465                interface_id,
1466                data,
1467                rssi: _,
1468                snr: _,
1469            } => {
1470                assert_eq!(interface_id, InterfaceId(8100));
1471                assert_eq!(data, payload);
1472            }
1473            other => panic!("expected Frame, got {:?}", other),
1474        }
1475    }
1476
1477    #[test]
1478    fn backbone_send_to_client() {
1479        let port = find_free_port();
1480        let (tx, rx) = crate::event::channel();
1481        let next_id = Arc::new(AtomicU64::new(8200));
1482
1483        let config = make_server_config(port, 82, None, None, None, BackboneAbuseConfig::default());
1484
1485        start(config, tx, next_id).unwrap();
1486        thread::sleep(Duration::from_millis(50));
1487
1488        let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1489        client
1490            .set_read_timeout(Some(Duration::from_secs(2)))
1491            .unwrap();
1492
1493        // Get writer from InterfaceUp
1494        let event = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1495        let mut writer = match event {
1496            Event::InterfaceUp(_, Some(w), _) => w,
1497            other => panic!("expected InterfaceUp with writer, got {:?}", other),
1498        };
1499
1500        // Send frame via writer
1501        let payload: Vec<u8> = (0..24).collect();
1502        writer.send_frame(&payload).unwrap();
1503
1504        // Read from client
1505        let mut buf = [0u8; 256];
1506        let n = client.read(&mut buf).unwrap();
1507        let expected = hdlc::frame(&payload);
1508        assert_eq!(&buf[..n], &expected[..]);
1509    }
1510
1511    #[test]
1512    fn backbone_multiple_clients() {
1513        let port = find_free_port();
1514        let (tx, rx) = crate::event::channel();
1515        let next_id = Arc::new(AtomicU64::new(8300));
1516
1517        let config = make_server_config(port, 83, None, None, None, BackboneAbuseConfig::default());
1518
1519        start(config, tx, next_id).unwrap();
1520        thread::sleep(Duration::from_millis(50));
1521
1522        let _client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1523        let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1524
1525        let mut ids = Vec::new();
1526        for _ in 0..2 {
1527            let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1528            match event {
1529                Event::InterfaceUp(id, _, _) => ids.push(id),
1530                other => panic!("expected InterfaceUp, got {:?}", other),
1531            }
1532        }
1533
1534        assert_eq!(ids.len(), 2);
1535        assert_ne!(ids[0], ids[1]);
1536    }
1537
1538    #[test]
1539    fn backbone_client_disconnect() {
1540        let port = find_free_port();
1541        let (tx, rx) = crate::event::channel();
1542        let next_id = Arc::new(AtomicU64::new(8400));
1543
1544        let config = make_server_config(port, 84, None, None, None, BackboneAbuseConfig::default());
1545
1546        start(config, tx, next_id).unwrap();
1547        thread::sleep(Duration::from_millis(50));
1548
1549        let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1550
1551        // Drain InterfaceUp
1552        let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1553
1554        // Disconnect
1555        drop(client);
1556
1557        // Should receive InterfaceDown
1558        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1559        assert!(
1560            matches!(event, Event::InterfaceDown(InterfaceId(8400))),
1561            "expected InterfaceDown(8400), got {:?}",
1562            event
1563        );
1564    }
1565
1566    #[test]
1567    fn backbone_epoll_multiplexing() {
1568        let port = find_free_port();
1569        let (tx, rx) = crate::event::channel();
1570        let next_id = Arc::new(AtomicU64::new(8500));
1571
1572        let config = make_server_config(port, 85, None, None, None, BackboneAbuseConfig::default());
1573
1574        start(config, tx, next_id).unwrap();
1575        thread::sleep(Duration::from_millis(50));
1576
1577        let mut client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1578        let mut client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1579
1580        // Drain both InterfaceUp events
1581        let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1582        let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1583
1584        // Both clients send data simultaneously
1585        let payload1: Vec<u8> = (0..24).collect();
1586        let payload2: Vec<u8> = (100..130).collect();
1587        client1.write_all(&hdlc::frame(&payload1)).unwrap();
1588        client2.write_all(&hdlc::frame(&payload2)).unwrap();
1589
1590        // Should receive both Frame events
1591        let mut received = Vec::new();
1592        for _ in 0..2 {
1593            let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1594            match event {
1595                Event::Frame { data, .. } => received.push(data),
1596                other => panic!("expected Frame, got {:?}", other),
1597            }
1598        }
1599        assert!(received.contains(&payload1));
1600        assert!(received.contains(&payload2));
1601    }
1602
1603    #[test]
1604    fn backbone_bind_port() {
1605        let port = find_free_port();
1606        let (tx, _rx) = crate::event::channel();
1607        let next_id = Arc::new(AtomicU64::new(8600));
1608
1609        let config = make_server_config(port, 86, None, None, None, BackboneAbuseConfig::default());
1610
1611        // Should not error
1612        start(config, tx, next_id).unwrap();
1613    }
1614
1615    #[test]
1616    fn backbone_hdlc_fragmented() {
1617        let port = find_free_port();
1618        let (tx, rx) = crate::event::channel();
1619        let next_id = Arc::new(AtomicU64::new(8700));
1620
1621        let config = make_server_config(port, 87, None, None, None, BackboneAbuseConfig::default());
1622
1623        start(config, tx, next_id).unwrap();
1624        thread::sleep(Duration::from_millis(50));
1625
1626        let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1627        client.set_nodelay(true).unwrap();
1628
1629        // Drain InterfaceUp
1630        let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1631
1632        // Send HDLC frame in two fragments
1633        let payload: Vec<u8> = (0..32).collect();
1634        let framed = hdlc::frame(&payload);
1635        let mid = framed.len() / 2;
1636
1637        client.write_all(&framed[..mid]).unwrap();
1638        thread::sleep(Duration::from_millis(50));
1639        client.write_all(&framed[mid..]).unwrap();
1640
1641        // Should receive reassembled frame
1642        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1643        match event {
1644            Event::Frame { data, .. } => {
1645                assert_eq!(data, payload);
1646            }
1647            other => panic!("expected Frame, got {:?}", other),
1648        }
1649    }
1650
1651    // -----------------------------------------------------------------------
1652    // Client mode tests
1653    // -----------------------------------------------------------------------
1654
1655    fn make_client_config(port: u16, id: u64) -> BackboneClientConfig {
1656        BackboneClientConfig {
1657            name: format!("test-bb-client-{}", port),
1658            target_host: "127.0.0.1".into(),
1659            target_port: port,
1660            interface_id: InterfaceId(id),
1661            reconnect_wait: Duration::from_millis(100),
1662            max_reconnect_tries: Some(2),
1663            connect_timeout: Duration::from_secs(2),
1664            transport_identity: None,
1665            runtime: Arc::new(Mutex::new(BackboneClientRuntime {
1666                reconnect_wait: Duration::from_millis(100),
1667                max_reconnect_tries: Some(2),
1668                connect_timeout: Duration::from_secs(2),
1669            })),
1670        }
1671    }
1672
1673    #[test]
1674    fn backbone_client_connect() {
1675        let port = find_free_port();
1676        let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
1677        let (tx, rx) = crate::event::channel();
1678
1679        let config = make_client_config(port, 9000);
1680        let _writer = start_client(config, tx).unwrap();
1681
1682        let _server_stream = listener.accept().unwrap();
1683
1684        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1685        assert!(matches!(event, Event::InterfaceUp(InterfaceId(9000), _, _)));
1686    }
1687
1688    #[test]
1689    fn backbone_client_receive_frame() {
1690        let port = find_free_port();
1691        let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
1692        let (tx, rx) = crate::event::channel();
1693
1694        let config = make_client_config(port, 9100);
1695        let _writer = start_client(config, tx).unwrap();
1696
1697        let (mut server_stream, _) = listener.accept().unwrap();
1698
1699        // Drain InterfaceUp
1700        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
1701
1702        // Send HDLC frame from server side (>= 19 bytes payload)
1703        let payload: Vec<u8> = (0..32).collect();
1704        server_stream.write_all(&hdlc::frame(&payload)).unwrap();
1705
1706        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1707        match event {
1708            Event::Frame {
1709                interface_id,
1710                data,
1711                rssi: _,
1712                snr: _,
1713            } => {
1714                assert_eq!(interface_id, InterfaceId(9100));
1715                assert_eq!(data, payload);
1716            }
1717            other => panic!("expected Frame, got {:?}", other),
1718        }
1719    }
1720
1721    #[test]
1722    fn backbone_client_send_frame() {
1723        let port = find_free_port();
1724        let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
1725        let (tx, _rx) = crate::event::channel();
1726
1727        let config = make_client_config(port, 9200);
1728        let mut writer = start_client(config, tx).unwrap();
1729
1730        let (mut server_stream, _) = listener.accept().unwrap();
1731        server_stream
1732            .set_read_timeout(Some(Duration::from_secs(2)))
1733            .unwrap();
1734
1735        let payload: Vec<u8> = (0..24).collect();
1736        writer.send_frame(&payload).unwrap();
1737
1738        let mut buf = [0u8; 256];
1739        let n = server_stream.read(&mut buf).unwrap();
1740        let expected = hdlc::frame(&payload);
1741        assert_eq!(&buf[..n], &expected[..]);
1742    }
1743
1744    #[test]
1745    fn backbone_max_connections_rejects_excess() {
1746        let port = find_free_port();
1747        let (tx, rx) = crate::event::channel();
1748        let next_id = Arc::new(AtomicU64::new(8800));
1749
1750        let config = make_server_config(
1751            port,
1752            88,
1753            Some(2),
1754            None,
1755            None,
1756            BackboneAbuseConfig::default(),
1757        );
1758
1759        start(config, tx, next_id).unwrap();
1760        thread::sleep(Duration::from_millis(50));
1761
1762        // Connect two clients (at limit)
1763        let _client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1764        let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1765
1766        // Drain both InterfaceUp events
1767        for _ in 0..2 {
1768            let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1769            assert!(matches!(event, Event::InterfaceUp(_, _, _)));
1770        }
1771
1772        // Third connection should be accepted at TCP level but immediately dropped
1773        let client3 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1774        client3
1775            .set_read_timeout(Some(Duration::from_millis(500)))
1776            .unwrap();
1777
1778        // Give server time to reject
1779        thread::sleep(Duration::from_millis(100));
1780
1781        // Should NOT receive a third InterfaceUp
1782        let result = recv_non_peer_event(&rx, Duration::from_millis(500));
1783        assert!(
1784            result.is_err(),
1785            "expected no InterfaceUp for rejected connection, got {:?}",
1786            result
1787        );
1788    }
1789
1790    #[test]
1791    fn backbone_max_connections_allows_after_disconnect() {
1792        let port = find_free_port();
1793        let (tx, rx) = crate::event::channel();
1794        let next_id = Arc::new(AtomicU64::new(8900));
1795
1796        let config = make_server_config(
1797            port,
1798            89,
1799            Some(1),
1800            None,
1801            None,
1802            BackboneAbuseConfig::default(),
1803        );
1804
1805        start(config, tx, next_id).unwrap();
1806        thread::sleep(Duration::from_millis(50));
1807
1808        // Connect first client
1809        let client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1810        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1811        assert!(matches!(event, Event::InterfaceUp(_, _, _)));
1812
1813        // Disconnect first client
1814        drop(client1);
1815
1816        // Wait for InterfaceDown
1817        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1818        assert!(matches!(event, Event::InterfaceDown(_)));
1819
1820        // Now a new connection should be accepted
1821        let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1822        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1823        assert!(
1824            matches!(event, Event::InterfaceUp(_, _, _)),
1825            "expected InterfaceUp after slot freed, got {:?}",
1826            event
1827        );
1828    }
1829
1830    #[test]
1831    fn backbone_client_reconnect() {
1832        let port = find_free_port();
1833        let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
1834        listener.set_nonblocking(false).unwrap();
1835        let (tx, rx) = crate::event::channel();
1836
1837        let config = make_client_config(port, 9300);
1838        let _writer = start_client(config, tx).unwrap();
1839
1840        // Accept first connection and immediately close it
1841        let (server_stream, _) = listener.accept().unwrap();
1842
1843        // Drain InterfaceUp
1844        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
1845
1846        drop(server_stream);
1847
1848        // Should get InterfaceDown
1849        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1850        assert!(matches!(event, Event::InterfaceDown(InterfaceId(9300))));
1851
1852        // Accept the reconnection
1853        let _server_stream2 = listener.accept().unwrap();
1854
1855        // Should get InterfaceUp again
1856        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1857        assert!(matches!(event, Event::InterfaceUp(InterfaceId(9300), _, _)));
1858    }
1859
1860    #[test]
1861    fn backbone_idle_timeout_disconnects_silent_client() {
1862        let port = find_free_port();
1863        let (tx, rx) = crate::event::channel();
1864        let next_id = Arc::new(AtomicU64::new(9400));
1865
1866        let config = make_server_config(
1867            port,
1868            94,
1869            None,
1870            Some(Duration::from_millis(150)),
1871            None,
1872            BackboneAbuseConfig::default(),
1873        );
1874
1875        start(config, tx, next_id).unwrap();
1876        thread::sleep(Duration::from_millis(50));
1877
1878        let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1879
1880        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1881        let client_id = match event {
1882            Event::InterfaceUp(id, _, _) => id,
1883            other => panic!("expected InterfaceUp, got {:?}", other),
1884        };
1885
1886        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1887        assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
1888    }
1889
1890    #[test]
1891    fn backbone_idle_timeout_ignores_client_after_data() {
1892        let port = find_free_port();
1893        let (tx, rx) = crate::event::channel();
1894        let next_id = Arc::new(AtomicU64::new(9500));
1895
1896        let config = make_server_config(
1897            port,
1898            95,
1899            None,
1900            Some(Duration::from_millis(200)),
1901            None,
1902            BackboneAbuseConfig::default(),
1903        );
1904
1905        start(config, tx, next_id).unwrap();
1906        thread::sleep(Duration::from_millis(50));
1907
1908        let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1909
1910        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1911        let client_id = match event {
1912            Event::InterfaceUp(id, _, _) => id,
1913            other => panic!("expected InterfaceUp, got {:?}", other),
1914        };
1915
1916        client.write_all(&hdlc::frame(&[1u8; 24])).unwrap();
1917
1918        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1919        match event {
1920            Event::Frame {
1921                interface_id,
1922                data,
1923                rssi: _,
1924                snr: _,
1925            } => {
1926                assert_eq!(interface_id, client_id);
1927                assert_eq!(data, vec![1u8; 24]);
1928            }
1929            other => panic!("expected Frame, got {:?}", other),
1930        }
1931
1932        let result = recv_non_peer_event(&rx, Duration::from_millis(500));
1933        assert!(
1934            result.is_err(),
1935            "expected no InterfaceDown after client sent data, got {:?}",
1936            result
1937        );
1938    }
1939
1940    #[test]
1941    fn backbone_runtime_idle_timeout_updates_live() {
1942        let port = find_free_port();
1943        let (tx, rx) = crate::event::channel();
1944        let next_id = Arc::new(AtomicU64::new(9650));
1945
1946        let config = make_server_config(port, 97, None, None, None, BackboneAbuseConfig::default());
1947        let runtime = Arc::clone(&config.runtime);
1948
1949        start(config, tx, next_id).unwrap();
1950        thread::sleep(Duration::from_millis(50));
1951
1952        let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1953        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1954        let client_id = match event {
1955            Event::InterfaceUp(id, _, _) => id,
1956            other => panic!("expected InterfaceUp, got {:?}", other),
1957        };
1958
1959        {
1960            let mut runtime = runtime.lock().unwrap();
1961            runtime.idle_timeout = Some(Duration::from_millis(150));
1962        }
1963
1964        let event = recv_non_peer_event(&rx, Duration::from_secs(4)).unwrap();
1965        assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
1966    }
1967
1968    #[test]
1969    fn backbone_write_stall_timeout_disconnects_unwritable_client() {
1970        let port = find_free_port();
1971        let (tx, rx) = crate::event::channel();
1972        let next_id = Arc::new(AtomicU64::new(9660));
1973
1974        let config = make_server_config(
1975            port,
1976            98,
1977            None,
1978            None,
1979            Some(Duration::from_millis(50)),
1980            BackboneAbuseConfig::default(),
1981        );
1982
1983        start(config, tx, next_id).unwrap();
1984        thread::sleep(Duration::from_millis(50));
1985
1986        let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1987        client
1988            .set_read_timeout(Some(Duration::from_millis(100)))
1989            .unwrap();
1990        let sock = SockRef::from(&client);
1991        sock.set_recv_buffer_size(4096).ok();
1992
1993        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1994        let (client_id, mut writer) = match event {
1995            Event::InterfaceUp(id, Some(writer), _) => (id, writer),
1996            other => panic!("expected InterfaceUp with writer, got {:?}", other),
1997        };
1998
1999        let payload = vec![0x55; 512 * 1024];
2000        let deadline = Instant::now() + Duration::from_secs(3);
2001        let mut stalled = false;
2002        while Instant::now() < deadline {
2003            match writer.send_frame(&payload) {
2004                Ok(()) => {}
2005                Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
2006                    thread::sleep(Duration::from_millis(10));
2007                }
2008                Err(ref e) if e.kind() == io::ErrorKind::TimedOut => {
2009                    stalled = true;
2010                    break;
2011                }
2012                Err(e) => panic!("unexpected send error: {}", e),
2013            }
2014        }
2015
2016        assert!(stalled, "expected writer to time out on persistent stall");
2017        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2018        assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
2019    }
2020
2021    /// Drain events matching a predicate, return the first match.
2022    fn wait_for<F>(rx: &mpsc::Receiver<Event>, timeout: Duration, mut pred: F) -> Option<Event>
2023    where
2024        F: FnMut(&Event) -> bool,
2025    {
2026        let deadline = Instant::now() + timeout;
2027        loop {
2028            let remaining = deadline.saturating_duration_since(Instant::now());
2029            if remaining.is_zero() {
2030                return None;
2031            }
2032            match rx.recv_timeout(remaining) {
2033                Ok(event) if pred(&event) => return Some(event),
2034                Ok(_) => continue,
2035                Err(_) => return None,
2036            }
2037        }
2038    }
2039
2040    #[test]
2041    fn backbone_write_stall_emits_peer_events() {
2042        let port = find_free_port();
2043        let (tx, rx) = crate::event::channel();
2044        let next_id = Arc::new(AtomicU64::new(9700));
2045
2046        let config = make_server_config(
2047            port,
2048            97,
2049            None,
2050            None,
2051            Some(Duration::from_millis(50)), // 50ms stall timeout
2052            BackboneAbuseConfig::default(),
2053        );
2054
2055        start(config, tx, next_id).unwrap();
2056        thread::sleep(Duration::from_millis(50));
2057
2058        // Connect a client that won't read (will cause write stall)
2059        let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2060        client
2061            .set_read_timeout(Some(Duration::from_millis(100)))
2062            .unwrap();
2063        let sock = SockRef::from(&client);
2064        sock.set_recv_buffer_size(4096).ok();
2065
2066        // Wait for InterfaceUp and grab writer
2067        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2068        let mut writer = match event {
2069            Event::InterfaceUp(_, Some(w), _) => w,
2070            other => panic!("expected InterfaceUp with writer, got {:?}", other),
2071        };
2072
2073        // Flood until stall
2074        let payload = vec![0x55; 512 * 1024];
2075        let deadline = Instant::now() + Duration::from_secs(3);
2076        while Instant::now() < deadline {
2077            match writer.send_frame(&payload) {
2078                Ok(()) | Err(_) => {
2079                    if Instant::now() + Duration::from_millis(10) > deadline {
2080                        break;
2081                    }
2082                    thread::sleep(Duration::from_millis(10));
2083                }
2084            }
2085        }
2086
2087        // Should see BackbonePeerWriteStall event
2088        let stall_event = wait_for(&rx, Duration::from_secs(3), |e| {
2089            matches!(e, Event::BackbonePeerWriteStall { .. })
2090        });
2091        assert!(
2092            stall_event.is_some(),
2093            "expected BackbonePeerWriteStall event"
2094        );
2095    }
2096
2097    #[test]
2098    fn backbone_blacklisted_peer_rejected_on_connect() {
2099        let port = find_free_port();
2100        let (tx, rx) = crate::event::channel();
2101        let next_id = Arc::new(AtomicU64::new(9800));
2102
2103        let config = make_server_config(port, 98, None, None, None, BackboneAbuseConfig::default());
2104        let peer_state = config.peer_state.clone();
2105
2106        start(config, tx.clone(), next_id).unwrap();
2107        thread::sleep(Duration::from_millis(50));
2108
2109        // First connection should succeed
2110        let client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2111        let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2112        assert!(
2113            matches!(event, Event::InterfaceUp(_, _, _)),
2114            "first connection should succeed"
2115        );
2116        drop(client1);
2117
2118        // Drain disconnect events
2119        thread::sleep(Duration::from_millis(100));
2120        while rx.try_recv().is_ok() {}
2121
2122        // Blacklist 127.0.0.1 via the peer monitor
2123        peer_state.lock().unwrap().blacklist(
2124            "127.0.0.1".parse().unwrap(),
2125            Duration::from_secs(60),
2126            "test blacklist".into(),
2127        );
2128
2129        // Second connection from same IP should be rejected (no InterfaceUp)
2130        let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2131        // Give poll loop time to reject
2132        thread::sleep(Duration::from_millis(200));
2133
2134        // Should NOT get an InterfaceUp — connection should have been rejected
2135        let event = rx.try_recv();
2136        match event {
2137            Ok(Event::InterfaceUp(_, _, _)) => {
2138                panic!("blacklisted peer should not get InterfaceUp")
2139            }
2140            _ => {} // Expected: no InterfaceUp
2141        }
2142    }
2143
2144    #[test]
2145    fn backbone_parse_config_reads_abuse_settings() {
2146        let factory = BackboneInterfaceFactory;
2147        let mut params = HashMap::new();
2148        params.insert("listen_ip".into(), "127.0.0.1".into());
2149        params.insert("listen_port".into(), "4242".into());
2150        params.insert("idle_timeout".into(), "15".into());
2151        params.insert("write_stall_timeout".into(), "45".into());
2152        params.insert("max_penalty_duration".into(), "3600".into());
2153
2154        let config = factory
2155            .parse_config("test-backbone", InterfaceId(97), &params)
2156            .unwrap();
2157        let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
2158
2159        match mode {
2160            BackboneMode::Server(config) => {
2161                assert_eq!(config.listen_ip, "127.0.0.1");
2162                assert_eq!(config.listen_port, 4242);
2163                assert_eq!(config.idle_timeout, Some(Duration::from_secs(15)));
2164                assert_eq!(config.write_stall_timeout, Some(Duration::from_secs(45)));
2165                assert_eq!(
2166                    config.abuse.max_penalty_duration,
2167                    Some(Duration::from_secs(3600))
2168                );
2169            }
2170            BackboneMode::Client(_, _) => panic!("expected server config"),
2171        }
2172    }
2173
2174    #[test]
2175    fn backbone_parse_config_reads_client_priority() {
2176        let factory = BackboneInterfaceFactory;
2177        let mut params = HashMap::new();
2178        params.insert("remote".into(), "example.com".into());
2179        params.insert("target_port".into(), "4242".into());
2180        params.insert("priority".into(), "87".into());
2181
2182        let config = factory
2183            .parse_config("test-backbone-client", InterfaceId(96), &params)
2184            .unwrap();
2185        let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
2186
2187        match mode {
2188            BackboneMode::Client(_, priority) => assert_eq!(priority, 87),
2189            BackboneMode::Server(_) => panic!("expected client config"),
2190        }
2191    }
2192
2193    #[test]
2194    fn backbone_parse_config_defaults_client_priority() {
2195        let factory = BackboneInterfaceFactory;
2196        let mut params = HashMap::new();
2197        params.insert("remote".into(), "example.com".into());
2198
2199        let config = factory
2200            .parse_config("test-backbone-client", InterfaceId(95), &params)
2201            .unwrap();
2202        let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
2203
2204        match mode {
2205            BackboneMode::Client(_, priority) => assert_eq!(priority, 60),
2206            BackboneMode::Server(_) => panic!("expected client config"),
2207        }
2208    }
2209
2210    #[test]
2211    fn backbone_parse_config_rejects_invalid_client_priority() {
2212        let factory = BackboneInterfaceFactory;
2213        for value in ["-1", "101", "fast"] {
2214            let mut params = HashMap::new();
2215            params.insert("remote".into(), "example.com".into());
2216            params.insert("priority".into(), value.into());
2217
2218            let err = match factory.parse_config("test-backbone-client", InterfaceId(94), &params) {
2219                Ok(_) => panic!("priority {value} should be rejected"),
2220                Err(err) => err,
2221            };
2222            assert!(
2223                err.contains("priority"),
2224                "unexpected error for {value}: {err}"
2225            );
2226        }
2227    }
2228}