1mod folded;
10mod transport;
11
12use std::io::{self, Read, Write};
13use std::os::fd::AsRawFd;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::{Arc, Mutex};
16use std::thread;
17use std::time::Duration;
18use transport::Transport;
19
20pub type DeviceWriter = crate::interface::device_writer::DeviceWriter<Transport>;
22
23use rns_core::transport::types::{AirtimeProfile, InterfaceId};
24
25use crate::event::{Event, EventSender, InterfaceTelemetry};
26use crate::interface::{lock_or_recover, Writer};
27use crate::rnode_kiss;
28use crate::serial::{Parity, SerialConfig};
29
30pub const FREQ_MIN: u32 = 137_000_000;
32pub const FREQ_MAX: u32 = 3_000_000_000;
33pub const BW_MIN: u32 = 7_800;
34pub const BW_MAX: u32 = 1_625_000;
35pub const SF_MIN: u8 = 5;
36pub const SF_MAX: u8 = 12;
37pub const CR_MIN: u8 = 5;
38pub const CR_MAX: u8 = 8;
39pub const TXPOWER_MIN: i8 = 0;
40pub const TXPOWER_MAX: i8 = 37;
41pub const HW_MTU: u32 = 508;
42pub const LORA_PREAMBLE_SYMBOLS: u16 = 8;
43pub const LORA_EXPLICIT_HEADER: bool = true;
44pub const LORA_CRC: bool = true;
45
46#[derive(Debug, Clone)]
48pub struct RNodeSubConfig {
49 pub name: String,
50 pub vport: u8,
51 pub outgoing: bool,
52 pub frequency: u32,
53 pub bandwidth: u32,
54 pub txpower: i8,
55 pub spreading_factor: u8,
56 pub coding_rate: u8,
57 pub flow_control: bool,
58 pub st_alock: Option<f32>,
59 pub lt_alock: Option<f32>,
60}
61
62#[derive(Debug, Clone)]
64pub struct RNodeConfig {
65 pub name: String,
66 pub port: String,
67 pub speed: u32,
68 pub subinterfaces: Vec<RNodeSubConfig>,
69 pub multi: bool,
70 pub id_interval: Option<u32>,
71 pub id_callsign: Option<Vec<u8>>,
72 pub base_interface_id: InterfaceId,
73 pub pre_opened_fd: Option<i32>,
76 pub underlay_mark: Option<u32>,
77 pub runtime: Arc<Mutex<RNodeRuntime>>,
78}
79
80#[derive(Debug, Clone)]
81pub struct RNodeRuntime {
82 pub sub: RNodeSubConfig,
83 pub writer: Option<Arc<Mutex<DeviceWriter>>>,
84}
85
86impl RNodeRuntime {
87 pub fn from_config(config: &RNodeConfig) -> Self {
88 Self {
89 sub: config
90 .subinterfaces
91 .first()
92 .cloned()
93 .unwrap_or_else(|| RNodeSubConfig {
94 name: config.name.clone(),
95 vport: 0,
96 outgoing: true,
97 frequency: 868_000_000,
98 bandwidth: 125_000,
99 txpower: 7,
100 spreading_factor: 8,
101 coding_rate: 5,
102 flow_control: false,
103 st_alock: None,
104 lt_alock: None,
105 }),
106 writer: None,
107 }
108 }
109}
110
111#[derive(Debug, Clone)]
112pub struct RNodeRuntimeConfigHandle {
113 pub interface_name: String,
114 pub runtime: Arc<Mutex<RNodeRuntime>>,
115 pub startup: RNodeRuntime,
116}
117
118impl Default for RNodeConfig {
119 fn default() -> Self {
120 let mut config = RNodeConfig {
121 name: String::new(),
122 port: String::new(),
123 speed: 115200,
124 subinterfaces: Vec::new(),
125 multi: false,
126 id_interval: None,
127 id_callsign: None,
128 base_interface_id: InterfaceId(0),
129 pre_opened_fd: None,
130 underlay_mark: None,
131 runtime: Arc::new(Mutex::new(RNodeRuntime {
132 sub: RNodeSubConfig {
133 name: String::new(),
134 vport: 0,
135 outgoing: true,
136 frequency: 868_000_000,
137 bandwidth: 125_000,
138 txpower: 7,
139 spreading_factor: 8,
140 coding_rate: 5,
141 flow_control: false,
142 st_alock: None,
143 lt_alock: None,
144 },
145 writer: None,
146 })),
147 };
148 let startup = RNodeRuntime::from_config(&config);
149 config.runtime = Arc::new(Mutex::new(startup));
150 config
151 }
152}
153
154pub fn validate_sub_config(sub: &RNodeSubConfig) -> Option<String> {
156 if sub.frequency < FREQ_MIN || sub.frequency > FREQ_MAX {
157 return Some(format!(
158 "Invalid frequency {} for {}",
159 sub.frequency, sub.name
160 ));
161 }
162 if sub.bandwidth < BW_MIN || sub.bandwidth > BW_MAX {
163 return Some(format!(
164 "Invalid bandwidth {} for {}",
165 sub.bandwidth, sub.name
166 ));
167 }
168 if sub.spreading_factor < SF_MIN || sub.spreading_factor > SF_MAX {
169 return Some(format!(
170 "Invalid SF {} for {}",
171 sub.spreading_factor, sub.name
172 ));
173 }
174 if sub.coding_rate < CR_MIN || sub.coding_rate > CR_MAX {
175 return Some(format!("Invalid CR {} for {}", sub.coding_rate, sub.name));
176 }
177 if sub.txpower < TXPOWER_MIN || sub.txpower > TXPOWER_MAX {
178 return Some(format!("Invalid TX power {} for {}", sub.txpower, sub.name));
179 }
180 if let Some(st) = sub.st_alock {
181 if !(0.0..=100.0).contains(&st) {
182 return Some(format!("Invalid ST airtime limit {} for {}", st, sub.name));
183 }
184 }
185 if let Some(lt) = sub.lt_alock {
186 if !(0.0..=100.0).contains(<) {
187 return Some(format!("Invalid LT airtime limit {} for {}", lt, sub.name));
188 }
189 }
190 None
191}
192
193pub fn estimate_lora_bitrate_bps(sub: &RNodeSubConfig) -> u64 {
197 let symbols_per_second = sub.bandwidth as f64 / (1u64 << sub.spreading_factor) as f64;
198 let bits_per_symbol = sub.spreading_factor as f64 * (4.0 / sub.coding_rate as f64);
199 (symbols_per_second * bits_per_symbol).round().max(1.0) as u64
200}
201
202pub fn lora_airtime_profile(sub: &RNodeSubConfig) -> AirtimeProfile {
203 AirtimeProfile::Lora {
204 bandwidth: sub.bandwidth,
205 spreading_factor: sub.spreading_factor,
206 coding_rate: sub.coding_rate,
207 preamble_symbols: LORA_PREAMBLE_SYMBOLS,
208 explicit_header: LORA_EXPLICIT_HEADER,
209 crc: LORA_CRC,
210 }
211}
212
213struct RNodeSubWriter {
216 confirmed_pending: Option<Vec<u8>>,
217 writer: Arc<Mutex<DeviceWriter>>,
218 index: u8,
219 flow_control: bool,
220 flow_state: Arc<Mutex<SubFlowState>>,
221 multi: bool,
222}
223
224struct SubFlowState {
225 ready: bool,
226 queue: std::collections::VecDeque<Vec<u8>>,
227}
228
229fn make_sub_writer(
230 writer: Arc<Mutex<DeviceWriter>>,
231 index: u8,
232 flow_control: bool,
233 flow_state: Arc<Mutex<SubFlowState>>,
234 multi: bool,
235) -> Box<dyn Writer> {
236 Box::new(RNodeSubWriter {
237 confirmed_pending: None,
238 writer,
239 index,
240 flow_control,
241 flow_state,
242 multi,
243 })
244}
245
246impl Writer for RNodeSubWriter {
247 fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
248 if !self.flow_control {
249 return self.send_frame(data);
250 }
251 self.confirmed_pending = Some(if self.multi {
252 rnode_kiss::rnode_multi_data_frame(self.index, data)
253 } else {
254 rnode_kiss::rnode_data_frame(self.index, data)
255 });
256 self.flush()
257 }
258
259 fn flush(&mut self) -> io::Result<()> {
260 if self.confirmed_pending.is_none() {
261 return Ok(());
262 }
263 let mut state = lock_or_recover(&self.flow_state, "rnode flow state");
264 if !state.ready || !state.queue.is_empty() {
265 return Err(io::ErrorKind::WouldBlock.into());
266 }
267 state.ready = false;
268 drop(state);
269 lock_or_recover(&self.writer, "rnode shared writer")
270 .write_all(&self.confirmed_pending.take().unwrap())
271 }
272 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
273 let frame = if self.multi {
274 rnode_kiss::rnode_multi_data_frame(self.index, data)
275 } else {
276 rnode_kiss::rnode_data_frame(self.index, data)
277 };
278 if self.flow_control {
279 let mut state = lock_or_recover(&self.flow_state, "rnode flow state");
280 if state.ready {
281 state.ready = false;
282 drop(state);
283 lock_or_recover(&self.writer, "rnode shared writer").write_all(&frame)
284 } else {
285 state.queue.push_back(data.to_vec());
286 Ok(())
287 }
288 } else {
289 lock_or_recover(&self.writer, "rnode shared writer").write_all(&frame)
290 }
291 }
292
293 fn enqueue_confirmed(
294 &mut self,
295 data: &[u8],
296 completion: &mut Option<crate::link_send::Completion>,
297 ) -> io::Result<()> {
298 let frame = self.frame(data);
302 let mut state = lock_or_recover(&self.flow_state, "rnode flow state");
303 let mut writer = lock_or_recover(&self.writer, "rnode shared writer");
304 if (self.flow_control && (!state.ready || !state.queue.is_empty()))
305 || !writer.fits(frame.len())
306 {
307 return Err(io::Error::new(
308 io::ErrorKind::WouldBlock,
309 "RNode cannot take a frame yet",
310 ));
311 }
312 writer.write_all(&frame)?;
313 if self.flow_control {
314 state.ready = false;
315 }
316 drop((writer, state));
317 if let Some(completion) = completion.take() {
318 completion.finish(Ok(()));
319 }
320 Ok(())
321 }
322
323 fn sends_inline(&self) -> bool {
324 crate::engine::FOLD_IO
326 }
327
328 fn flush_pending(&mut self) {
329 lock_or_recover(&self.writer, "rnode shared writer").drain();
330 }
331}
332
333impl RNodeSubWriter {
334 fn frame(&self, data: &[u8]) -> Vec<u8> {
335 if self.multi {
336 rnode_kiss::rnode_multi_data_frame(self.index, data)
337 } else {
338 rnode_kiss::rnode_data_frame(self.index, data)
339 }
340 }
341}
342
343type RNodeWriters = Vec<(InterfaceId, Box<dyn Writer>)>;
344
345pub fn start(
352 config: RNodeConfig,
353 tx: EventSender,
354) -> io::Result<Vec<(InterfaceId, Box<dyn Writer>)>> {
355 start_with_control(config, tx).map(|(writers, _)| writers)
356}
357
358fn start_with_control(
359 config: RNodeConfig,
360 tx: EventSender,
361) -> io::Result<(RNodeWriters, super::ListenerControl)> {
362 for sub in &config.subinterfaces {
364 if let Some(err) = validate_sub_config(sub) {
365 return Err(io::Error::new(io::ErrorKind::InvalidInput, err));
366 }
367 }
368
369 if config.subinterfaces.is_empty() {
370 return Err(io::Error::new(
371 io::ErrorKind::InvalidInput,
372 "No subinterfaces configured",
373 ));
374 }
375
376 let (reader_file, writer_transport) = if let Some(fd) = config.pre_opened_fd {
377 Transport::open_from_fd(fd)?
379 } else {
380 let serial_config = SerialConfig {
381 path: config.port.clone(),
382 baud: config.speed,
383 data_bits: 8,
384 parity: Parity::None,
385 stop_bits: 1,
386 };
387 Transport::open(&serial_config, config.underlay_mark)?
388 };
389 let shared_writer = Arc::new(Mutex::new(if crate::engine::FOLD_IO {
390 DeviceWriter::driven(
391 writer_transport,
392 Arc::clone(tx.poller()),
393 crate::interface::loop_writer::write_key(config.base_interface_id),
394 &config.name,
395 )
396 } else {
397 DeviceWriter::new(writer_transport)
398 }));
399
400 let num_subs = config.subinterfaces.len();
402 let mut writers: Vec<(InterfaceId, Box<dyn Writer>)> = Vec::with_capacity(num_subs);
403 let mut flow_states: Vec<Arc<Mutex<SubFlowState>>> = Vec::with_capacity(num_subs);
404
405 for (i, sub) in config.subinterfaces.iter().enumerate() {
406 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
407 let flow_state = Arc::new(Mutex::new(SubFlowState {
408 ready: true,
409 queue: std::collections::VecDeque::new(),
410 }));
411 flow_states.push(flow_state.clone());
412 let sub_writer = make_sub_writer(
413 shared_writer.clone(),
414 sub.vport,
415 sub.flow_control,
416 flow_state,
417 config.multi,
418 );
419 writers.push((sub_id, sub_writer));
420 }
421
422 let reader_shared_writer = shared_writer.clone();
424 {
425 let mut runtime = lock_or_recover(&config.runtime, "rnode runtime");
426 runtime.writer = Some(shared_writer.clone());
427 runtime.sub = config
428 .subinterfaces
429 .first()
430 .cloned()
431 .unwrap_or(runtime.sub.clone());
432 }
433 let running = Arc::new(AtomicBool::new(true));
434 if crate::engine::FOLD_IO {
435 folded::RNodeLink::new(config, tx, shared_writer, flow_states, Arc::clone(&running))
436 .start(reader_file)?;
437 return Ok((writers, super::ListenerControl::with_running_flag(running)));
438 }
439 let reader_config = config.clone();
440 let reader_flow_states = flow_states;
441 let reader_running = Arc::clone(&running);
442 thread::Builder::new()
443 .name(format!("rnode-reader-{}", config.base_interface_id.0))
444 .spawn(move || {
445 reader_loop(
446 reader_file,
447 reader_shared_writer,
448 reader_config,
449 tx,
450 reader_flow_states,
451 reader_running,
452 );
453 })?;
454
455 let keepalive_writer = shared_writer.clone();
458 let keepalive_name = config.name.clone();
459 let keepalive_running = Arc::clone(&running);
460 thread::Builder::new()
461 .name(format!("rnode-keepalive-{}", config.base_interface_id.0))
462 .spawn(move || {
463 let detect = rnode_kiss::detect_request();
464 while keepalive_running.load(Ordering::Relaxed) {
465 if !wait_while_running(&keepalive_running, Duration::from_secs(15)) {
466 break;
467 }
468 if let Err(e) =
469 lock_or_recover(&keepalive_writer, "rnode shared writer").write_all(&detect)
470 {
471 log::debug!("[{}] keepalive write failed: {}", keepalive_name, e);
472 }
473 }
474 })?;
475
476 Ok((writers, super::ListenerControl::with_running_flag(running)))
477}
478
479fn wait_while_running(running: &AtomicBool, duration: Duration) -> bool {
480 let deadline = std::time::Instant::now() + duration;
481 while running.load(Ordering::Relaxed) {
482 let remaining = deadline.saturating_duration_since(std::time::Instant::now());
483 if remaining.is_zero() {
484 return true;
485 }
486 thread::sleep(remaining.min(Duration::from_millis(100)));
487 }
488 false
489}
490
491fn reader_loop(
493 mut reader: Transport,
494 writer: Arc<Mutex<DeviceWriter>>,
495 config: RNodeConfig,
496 tx: EventSender,
497 flow_states: Vec<Arc<Mutex<SubFlowState>>>,
498 running: Arc<AtomicBool>,
499) {
500 const RECONNECT_INITIAL_DELAY: Duration = Duration::from_millis(200);
501 const RECONNECT_MAX_DELAY: Duration = Duration::from_secs(2);
502 if !wait_while_running(&running, Duration::from_secs(2)) {
504 return;
505 }
506 let mut connected_once = false;
507 let mut last_rssi: Option<i16> = None;
511 let mut last_snr: Option<f32> = None;
512 if let Err(e) = detect_and_configure(&mut reader, &writer, &config) {
513 log::error!("[{}] initial RNode setup failed: {}", config.name, e);
514 return;
515 }
516 signal_interface_up(&tx, &config, &writer, &flow_states, connected_once);
517 connected_once = true;
518 while running.load(Ordering::Relaxed) {
519 let mut decoder = rnode_kiss::RNodeDecoder::new();
520 let mut buf = [0u8; 4096];
521 let disconnected = loop {
522 if !running.load(Ordering::Relaxed) {
523 return;
524 }
525 if !reader_ready(&reader, Duration::from_millis(200)) {
526 continue;
527 }
528 match reader.read(&mut buf) {
529 Ok(0) => {
530 if !running.load(Ordering::Relaxed) {
531 return;
532 }
533 log::warn!("[{}] serial port closed", config.name);
534 signal_interface_down(&tx, &config);
535 break true;
536 }
537 Ok(n) => {
538 for event in decoder.feed(&buf[..n]) {
539 let phy_stat = matches!(
540 &event,
541 rnode_kiss::RNodeEvent::StatRssi(_)
542 | rnode_kiss::RNodeEvent::StatSnr(_)
543 );
544 if update_pending_rx_metadata(&event, &mut last_rssi, &mut last_snr) {
545 if phy_stat {
546 publish_phy_telemetry(&tx, &config, last_rssi, last_snr);
547 }
548 continue;
549 }
550 match event {
551 rnode_kiss::RNodeEvent::DataFrame { index, data } => {
552 let Some(position) = config
553 .subinterfaces
554 .iter()
555 .position(|sub| usize::from(sub.vport) == index)
556 else {
557 continue;
558 };
559 let sub_id =
560 InterfaceId(config.base_interface_id.0 + position as u64);
561 if tx
562 .send(Event::Frame {
563 interface_id: sub_id,
564 data: data.into(),
565 rssi: last_rssi,
566 snr: last_snr,
567 })
568 .is_err()
569 {
570 return;
571 }
572 last_rssi = None;
573 last_snr = None;
574 }
575 rnode_kiss::RNodeEvent::Ready => {
576 for (i, fs) in flow_states.iter().enumerate() {
578 if config.subinterfaces[i].flow_control {
579 process_flow_queue(
580 fs,
581 &writer,
582 config.subinterfaces[i].vport,
583 config.multi,
584 );
585 }
586 }
587 }
588 rnode_kiss::RNodeEvent::Error(code) => {
589 log::error!("[{}] RNode error: 0x{:02X}", config.name, code);
590 }
591 _ => {
592 }
594 }
595 }
596 }
597 Err(e) => {
598 if !running.load(Ordering::Relaxed) {
599 return;
600 }
601 log::error!("[{}] serial read error: {}", config.name, e);
602 signal_interface_down(&tx, &config);
603 break true;
604 }
605 }
606 };
607
608 clear_pending_rx_metadata(&mut last_rssi, &mut last_snr);
609
610 if !running.load(Ordering::Relaxed) || !disconnected || config.pre_opened_fd.is_some() {
611 return;
612 }
613
614 let mut backoff = RECONNECT_INITIAL_DELAY;
615 while running.load(Ordering::Relaxed) {
616 match reopen_connection(&config, &writer) {
617 Ok(new_reader) => {
618 reset_flow_states(&flow_states);
619 reader = new_reader;
620 if let Err(e) = detect_and_configure(&mut reader, &writer, &config) {
621 log::warn!("[{}] reconnect configure failed: {}", config.name, e);
622 if !wait_while_running(&running, backoff) {
623 return;
624 }
625 backoff = std::cmp::min(backoff.saturating_mul(2), RECONNECT_MAX_DELAY);
626 continue;
627 }
628 signal_interface_up(&tx, &config, &writer, &flow_states, connected_once);
629 break;
630 }
631 Err(e) => {
632 log::warn!("[{}] reconnect open failed: {}", config.name, e);
633 if !wait_while_running(&running, backoff) {
634 return;
635 }
636 backoff = std::cmp::min(backoff.saturating_mul(2), RECONNECT_MAX_DELAY);
637 }
638 }
639 }
640 }
641}
642
643fn reader_ready(reader: &Transport, timeout: Duration) -> bool {
644 let mut pfd = libc::pollfd {
645 fd: reader.as_raw_fd(),
646 events: libc::POLLIN,
647 revents: 0,
648 };
649 let timeout_ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
650 unsafe { libc::poll(&mut pfd, 1, timeout_ms) > 0 }
651}
652
653fn detect_and_configure(
654 reader: &mut Transport,
655 writer: &Arc<Mutex<DeviceWriter>>,
656 config: &RNodeConfig,
657) -> io::Result<()> {
658 let detect_cmd = rnode_kiss::detect_request();
659 let mut cmd = detect_cmd;
660 cmd.extend_from_slice(&rnode_kiss::rnode_command(
661 rnode_kiss::CMD_FW_VERSION,
662 &[0x00],
663 ));
664 cmd.extend_from_slice(&rnode_kiss::rnode_command(
665 rnode_kiss::CMD_FW_DETAIL,
666 &[0x00],
667 ));
668 cmd.extend_from_slice(&rnode_kiss::rnode_command(
669 rnode_kiss::CMD_PLATFORM,
670 &[0x00],
671 ));
672 cmd.extend_from_slice(&rnode_kiss::rnode_command(rnode_kiss::CMD_MCU, &[0x00]));
673
674 lock_or_recover(writer, "rnode shared writer").write_all(&cmd)?;
675
676 let mut decoder = rnode_kiss::RNodeDecoder::new();
677 let mut buf = [0u8; 4096];
678 let mut detected = false;
679 let detect_start = std::time::Instant::now();
680 let detect_timeout = Duration::from_secs(5);
681
682 while !detected && detect_start.elapsed() < detect_timeout {
683 match reader.read(&mut buf) {
684 Ok(0) => {
685 return Err(io::Error::new(
686 io::ErrorKind::UnexpectedEof,
687 "serial port closed during detect",
688 ));
689 }
690 Ok(n) => {
691 for event in decoder.feed(&buf[..n]) {
692 match event {
693 rnode_kiss::RNodeEvent::Detected(true) => {
694 detected = true;
695 log::info!("[{}] RNode device detected", config.name);
696 }
697 rnode_kiss::RNodeEvent::FirmwareVersion { major, minor } => {
698 log::info!("[{}] firmware version {}.{}", config.name, major, minor);
699 }
700 rnode_kiss::RNodeEvent::FirmwareDetail(ref detail) => {
701 log::info!("[{}] firmware detail: {}", config.name, detail);
702 }
703 rnode_kiss::RNodeEvent::Platform(p) => {
704 log::info!("[{}] platform: 0x{:02X}", config.name, p);
705 }
706 rnode_kiss::RNodeEvent::Mcu(m) => {
707 log::info!("[{}] MCU: 0x{:02X}", config.name, m);
708 }
709 _ => {}
710 }
711 }
712 }
713 Err(e) => {
714 return Err(io::Error::new(
715 e.kind(),
716 format!("serial read error during detect: {}", e),
717 ));
718 }
719 }
720 }
721
722 if !detected {
723 return Err(io::Error::new(
724 io::ErrorKind::TimedOut,
725 "RNode detection timed out",
726 ));
727 }
728
729 for sub in &config.subinterfaces {
730 configure_subinterface(writer, sub.vport, sub, config.multi)?;
731 }
732
733 thread::sleep(Duration::from_millis(300));
734 log::info!(
735 "[{}] RNode configured with {} subinterface(s)",
736 config.name,
737 config.subinterfaces.len()
738 );
739 Ok(())
740}
741
742fn signal_interface_down(tx: &EventSender, config: &RNodeConfig) {
743 for i in 0..config.subinterfaces.len() {
744 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
745 let _ = tx.send(Event::InterfaceDown(sub_id));
746 }
747}
748
749fn update_pending_rx_metadata(
750 event: &rnode_kiss::RNodeEvent,
751 last_rssi: &mut Option<i16>,
752 last_snr: &mut Option<f32>,
753) -> bool {
754 match event {
755 rnode_kiss::RNodeEvent::StatRssi(rssi) => {
756 *last_rssi = Some(*rssi as i16 - 157);
757 true
758 }
759 rnode_kiss::RNodeEvent::StatSnr(snr) => {
760 *last_snr = Some(*snr as f32 * 0.25);
761 true
762 }
763 _ => false,
764 }
765}
766
767fn signal_interface_up(
768 tx: &EventSender,
769 config: &RNodeConfig,
770 writer: &Arc<Mutex<DeviceWriter>>,
771 flow_states: &[Arc<Mutex<SubFlowState>>],
772 reconnected: bool,
773) {
774 for (i, flow_state) in flow_states.iter().enumerate() {
775 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
776 let new_writer = reconnected.then(|| {
777 make_sub_writer(
778 writer.clone(),
779 config.subinterfaces[i].vport,
780 config.subinterfaces[i].flow_control,
781 flow_state.clone(),
782 config.multi,
783 )
784 });
785 let _ = tx.send(Event::InterfaceUp(sub_id, new_writer, None));
786 }
787 publish_phy_telemetry(tx, config, None, None);
788}
789
790fn signal_quality(spreading_factor: u8, snr: f32) -> f64 {
793 let sfs = spreading_factor.saturating_sub(7) as f64;
794 let q_snr_min = -9.0 - sfs * 2.0;
795 let q_snr_max = 6.0;
796 let span = q_snr_max - q_snr_min;
797 if span <= 0.0 {
798 return 0.0;
799 }
800 let quality = (((f64::from(snr) - q_snr_min) / span) * 100.0).clamp(0.0, 100.0);
801 (quality * 10.0).round() / 10.0
802}
803
804fn publish_phy_telemetry(
805 tx: &EventSender,
806 config: &RNodeConfig,
807 rssi: Option<i16>,
808 snr: Option<f32>,
809) {
810 for event in phy_telemetry(config, rssi, snr) {
811 let _ = tx.send(event);
812 }
813}
814
815fn phy_telemetry(
817 config: &RNodeConfig,
818 rssi: Option<i16>,
819 snr: Option<f32>,
820) -> impl Iterator<Item = Event> + '_ {
821 config
822 .subinterfaces
823 .iter()
824 .enumerate()
825 .map(move |(i, sub)| {
826 let telemetry = InterfaceTelemetry {
827 rssi,
828 snr,
829 signal_quality: snr.map(|snr| signal_quality(sub.spreading_factor, snr)),
830 frequency: Some(sub.frequency),
831 bandwidth: Some(sub.bandwidth),
832 txpower: Some(sub.txpower),
833 spreading_factor: Some(sub.spreading_factor),
834 coding_rate: Some(sub.coding_rate),
835 channel: None,
836 modulation: Some("LoRa".to_string()),
837 ..Default::default()
838 };
839 Event::InterfaceTelemetry {
840 interface_id: InterfaceId(config.base_interface_id.0 + i as u64),
841 telemetry: Box::new(telemetry),
842 }
843 })
844}
845
846fn reset_flow_states(flow_states: &[Arc<Mutex<SubFlowState>>]) {
847 for flow_state in flow_states {
848 let mut state = lock_or_recover(flow_state, "rnode flow state");
849 state.ready = true;
850 state.queue.clear();
851 }
852}
853
854fn clear_pending_rx_metadata(last_rssi: &mut Option<i16>, last_snr: &mut Option<f32>) {
855 *last_rssi = None;
856 *last_snr = None;
857}
858
859fn reopen_connection(
860 config: &RNodeConfig,
861 writer: &Arc<Mutex<DeviceWriter>>,
862) -> io::Result<Transport> {
863 let serial_config = SerialConfig {
864 path: config.port.clone(),
865 baud: config.speed,
866 data_bits: 8,
867 parity: Parity::None,
868 stop_bits: 1,
869 };
870
871 let (reader, new_writer) = Transport::open(&serial_config, config.underlay_mark)?;
872 lock_or_recover(writer, "rnode shared writer").replace(new_writer);
873 Ok(reader)
874}
875
876pub(crate) fn configure_subinterface(
878 writer: &Arc<Mutex<DeviceWriter>>,
879 index: u8,
880 sub: &RNodeSubConfig,
881 multi: bool,
882) -> io::Result<()> {
883 let mut w = lock_or_recover(writer, "rnode shared writer");
884
885 let freq_bytes = [
887 (sub.frequency >> 24) as u8,
888 (sub.frequency >> 16) as u8,
889 (sub.frequency >> 8) as u8,
890 (sub.frequency & 0xFF) as u8,
891 ];
892 let bw_bytes = [
893 (sub.bandwidth >> 24) as u8,
894 (sub.bandwidth >> 16) as u8,
895 (sub.bandwidth >> 8) as u8,
896 (sub.bandwidth & 0xFF) as u8,
897 ];
898 let txp = if sub.txpower < 0 {
899 (sub.txpower as i16 + 256) as u8
900 } else {
901 sub.txpower as u8
902 };
903
904 if multi {
905 w.write_all(&rnode_kiss::rnode_select_command(
906 index,
907 rnode_kiss::CMD_FREQUENCY,
908 &freq_bytes,
909 ))?;
910 w.write_all(&rnode_kiss::rnode_select_command(
911 index,
912 rnode_kiss::CMD_BANDWIDTH,
913 &bw_bytes,
914 ))?;
915 w.write_all(&rnode_kiss::rnode_select_command(
916 index,
917 rnode_kiss::CMD_TXPOWER,
918 &[txp],
919 ))?;
920 w.write_all(&rnode_kiss::rnode_select_command(
921 index,
922 rnode_kiss::CMD_SF,
923 &[sub.spreading_factor],
924 ))?;
925 w.write_all(&rnode_kiss::rnode_select_command(
926 index,
927 rnode_kiss::CMD_CR,
928 &[sub.coding_rate],
929 ))?;
930 } else {
931 w.write_all(&rnode_kiss::rnode_command(
932 rnode_kiss::CMD_FREQUENCY,
933 &freq_bytes,
934 ))?;
935 w.write_all(&rnode_kiss::rnode_command(
936 rnode_kiss::CMD_BANDWIDTH,
937 &bw_bytes,
938 ))?;
939 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_TXPOWER, &[txp]))?;
940 w.write_all(&rnode_kiss::rnode_command(
941 rnode_kiss::CMD_SF,
942 &[sub.spreading_factor],
943 ))?;
944 w.write_all(&rnode_kiss::rnode_command(
945 rnode_kiss::CMD_CR,
946 &[sub.coding_rate],
947 ))?;
948 }
949
950 if let Some(st) = sub.st_alock {
952 let st_val = (st * 100.0) as u16;
953 let st_bytes = [(st_val >> 8) as u8, (st_val & 0xFF) as u8];
954 if multi {
955 w.write_all(&rnode_kiss::rnode_select_command(
956 index,
957 rnode_kiss::CMD_ST_ALOCK,
958 &st_bytes,
959 ))?;
960 } else {
961 w.write_all(&rnode_kiss::rnode_command(
962 rnode_kiss::CMD_ST_ALOCK,
963 &st_bytes,
964 ))?;
965 }
966 }
967 if let Some(lt) = sub.lt_alock {
968 let lt_val = (lt * 100.0) as u16;
969 let lt_bytes = [(lt_val >> 8) as u8, (lt_val & 0xFF) as u8];
970 if multi {
971 w.write_all(&rnode_kiss::rnode_select_command(
972 index,
973 rnode_kiss::CMD_LT_ALOCK,
974 <_bytes,
975 ))?;
976 } else {
977 w.write_all(&rnode_kiss::rnode_command(
978 rnode_kiss::CMD_LT_ALOCK,
979 <_bytes,
980 ))?;
981 }
982 }
983
984 if multi {
986 w.write_all(&rnode_kiss::rnode_select_command(
987 index,
988 rnode_kiss::CMD_RADIO_STATE,
989 &[rnode_kiss::RADIO_STATE_ON],
990 ))?;
991 } else {
992 w.write_all(&rnode_kiss::rnode_command(
993 rnode_kiss::CMD_RADIO_STATE,
994 &[rnode_kiss::RADIO_STATE_ON],
995 ))?;
996 }
997
998 Ok(())
999}
1000
1001fn process_flow_queue(
1003 flow_state: &Arc<Mutex<SubFlowState>>,
1004 writer: &Arc<Mutex<DeviceWriter>>,
1005 index: u8,
1006 multi: bool,
1007) {
1008 let mut state = lock_or_recover(flow_state, "rnode flow state");
1009 if let Some(data) = state.queue.pop_front() {
1010 state.ready = false;
1011 drop(state);
1012 let frame = if multi {
1013 rnode_kiss::rnode_multi_data_frame(index, &data)
1014 } else {
1015 rnode_kiss::rnode_data_frame(index, &data)
1016 };
1017 let _ = lock_or_recover(writer, "rnode shared writer").write_all(&frame);
1018 } else {
1019 state.ready = true;
1020 }
1021}
1022
1023use super::{
1026 ConfigSection, InterfaceConfigData, InterfaceFactory, StartContext, StartResult, SubInterface,
1027};
1028use rns_core::transport::types::InterfaceInfo;
1029use std::collections::HashMap;
1030
1031pub struct RNodeFactory;
1033
1034impl InterfaceFactory for RNodeFactory {
1035 fn type_name(&self) -> &str {
1036 "RNodeInterface"
1037 }
1038
1039 fn default_ifac_size(&self) -> usize {
1040 8
1041 }
1042
1043 fn parse_config(
1044 &self,
1045 name: &str,
1046 id: InterfaceId,
1047 params: &HashMap<String, String>,
1048 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1049 let pre_opened_fd = params.get("fd").and_then(|v| v.parse::<i32>().ok());
1050
1051 let port = params
1052 .get("port")
1053 .cloned()
1054 .or_else(|| pre_opened_fd.map(|_| "usb-bridge".to_string()))
1055 .ok_or_else(|| "RNodeInterface requires 'port' or 'fd'".to_string())?;
1056
1057 let speed = params
1058 .get("speed")
1059 .and_then(|v| v.parse().ok())
1060 .unwrap_or(115200u32);
1061
1062 let frequency = params
1063 .get("frequency")
1064 .and_then(|v| v.parse().ok())
1065 .unwrap_or(868_000_000u32);
1066
1067 let bandwidth = params
1068 .get("bandwidth")
1069 .and_then(|v| v.parse().ok())
1070 .unwrap_or(125_000u32);
1071
1072 let txpower = params
1073 .get("txpower")
1074 .and_then(|v| v.parse().ok())
1075 .unwrap_or(7i8);
1076
1077 let spreading_factor = params
1078 .get("spreadingfactor")
1079 .or_else(|| params.get("spreading_factor"))
1080 .and_then(|v| v.parse().ok())
1081 .unwrap_or(8u8);
1082
1083 let coding_rate = params
1084 .get("codingrate")
1085 .or_else(|| params.get("coding_rate"))
1086 .and_then(|v| v.parse().ok())
1087 .unwrap_or(5u8);
1088
1089 let flow_control = params
1090 .get("flow_control")
1091 .and_then(|v| crate::config::parse_bool_pub(v))
1092 .unwrap_or(false);
1093
1094 let st_alock = params.get("st_alock").and_then(|v| v.parse().ok());
1095
1096 let lt_alock = params.get("lt_alock").and_then(|v| v.parse().ok());
1097
1098 let id_interval = params.get("id_interval").and_then(|v| v.parse().ok());
1099
1100 let id_callsign = params.get("id_callsign").map(|v| v.as_bytes().to_vec());
1101
1102 let sub = RNodeSubConfig {
1103 name: name.to_string(),
1104 vport: 0,
1105 outgoing: true,
1106 frequency,
1107 bandwidth,
1108 txpower,
1109 spreading_factor,
1110 coding_rate,
1111 flow_control,
1112 st_alock,
1113 lt_alock,
1114 };
1115
1116 Ok(Box::new(RNodeConfig {
1117 name: name.to_string(),
1118 port,
1119 speed,
1120 subinterfaces: vec![sub],
1121 multi: false,
1122 id_interval,
1123 id_callsign,
1124 base_interface_id: id,
1125 pre_opened_fd,
1126 underlay_mark: None,
1127 runtime: Arc::new(Mutex::new(RNodeRuntime {
1128 sub: RNodeSubConfig {
1129 name: name.to_string(),
1130 vport: 0,
1131 outgoing: true,
1132 frequency,
1133 bandwidth,
1134 txpower,
1135 spreading_factor,
1136 coding_rate,
1137 flow_control,
1138 st_alock,
1139 lt_alock,
1140 },
1141 writer: None,
1142 })),
1143 }))
1144 }
1145
1146 fn start(
1147 &self,
1148 config: Box<dyn InterfaceConfigData>,
1149 ctx: StartContext,
1150 ) -> std::io::Result<StartResult> {
1151 let mut rnode_config = *config.into_any().downcast::<RNodeConfig>().map_err(|_| {
1152 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
1153 })?;
1154 rnode_config.underlay_mark = ctx.underlay_mark;
1155
1156 let sub_bitrates: Vec<u64> = rnode_config
1157 .subinterfaces
1158 .iter()
1159 .map(estimate_lora_bitrate_bps)
1160 .collect();
1161 let airtime_profiles: Vec<AirtimeProfile> = rnode_config
1162 .subinterfaces
1163 .iter()
1164 .map(lora_airtime_profile)
1165 .collect();
1166
1167 let (pairs, control) = start_with_control(rnode_config.clone(), ctx.tx)?;
1168
1169 let mut subs = Vec::with_capacity(pairs.len());
1170 for (index, (sub_id, writer)) in pairs.into_iter().enumerate() {
1171 let sub_name = rnode_config.subinterfaces[index].name.clone();
1172
1173 let info = InterfaceInfo {
1174 stamp_costs: Default::default(),
1175 id: sub_id,
1176 name: sub_name,
1177 mode: ctx.mode,
1178 gravity: ctx.gravity,
1179 recursive_prs: ctx.recursive_prs,
1180 announces_from_internal: ctx.announces_from_internal,
1181 announces_to_internal: ctx.announces_to_internal,
1182 out_capable: rnode_config.subinterfaces[index].outgoing,
1183 in_capable: true,
1184 bitrate: sub_bitrates.get(index).copied(),
1185 airtime_profile: airtime_profiles.get(index).copied(),
1186 announce_rate_target: None,
1187 announce_rate_grace: 0,
1188 announce_rate_penalty: 0.0,
1189 announce_cap: rns_core::constants::ANNOUNCE_CAP,
1190 is_local_client: false,
1191 wants_tunnel: false,
1192 tunnel_id: None,
1193 mtu: rns_core::constants::MTU as u32,
1194 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
1195 ia_freq: 0.0,
1196 ip_freq: 0.0,
1197 op_freq: 0.0,
1198 op_samples: 0,
1199 started: crate::time::now(),
1200 };
1201
1202 subs.push(SubInterface {
1203 id: sub_id,
1204 info,
1205 writer,
1206 interface_type_name: if rnode_config.multi {
1207 "RNodeMultiInterface"
1208 } else {
1209 "RNodeInterface"
1210 }
1211 .to_string(),
1212 });
1213 }
1214
1215 Ok(StartResult::Multi {
1216 subinterfaces: subs,
1217 control: Some(control),
1218 })
1219 }
1220}
1221
1222pub struct RNodeMultiFactory;
1224
1225impl InterfaceFactory for RNodeMultiFactory {
1226 fn type_name(&self) -> &str {
1227 "RNodeMultiInterface"
1228 }
1229
1230 fn default_ifac_size(&self) -> usize {
1231 8
1232 }
1233
1234 fn parse_config(
1235 &self,
1236 name: &str,
1237 id: InterfaceId,
1238 params: &HashMap<String, String>,
1239 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1240 self.parse_config_section(
1241 name,
1242 id,
1243 ConfigSection {
1244 params,
1245 children: &[],
1246 },
1247 )
1248 }
1249
1250 fn parse_config_section(
1251 &self,
1252 name: &str,
1253 id: InterfaceId,
1254 section: ConfigSection<'_>,
1255 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1256 let base = RNodeFactory.parse_config(name, id, section.params)?;
1257 let mut config = *base
1258 .into_any()
1259 .downcast::<RNodeConfig>()
1260 .map_err(|_| "wrong RNode config type".to_string())?;
1261
1262 let mut subinterfaces = Vec::new();
1263 for child in section.children {
1264 let enabled = child
1265 .params
1266 .get("interface_enabled")
1267 .or_else(|| child.params.get("enabled"))
1268 .and_then(|value| crate::config::parse_bool_pub(value))
1269 .unwrap_or(true);
1270 if !enabled {
1271 continue;
1272 }
1273 if subinterfaces.len() >= 11 {
1274 return Err("RNodeMultiInterface supports at most 11 enabled subinterfaces".into());
1275 }
1276 let vport = child
1277 .params
1278 .get("vport")
1279 .ok_or_else(|| format!("subinterface '{}' requires 'vport'", child.name))?
1280 .parse::<u8>()
1281 .map_err(|_| format!("invalid RNode virtual port for '{}'", child.name))?;
1282 if vport > 10
1283 || subinterfaces
1284 .iter()
1285 .any(|sub: &RNodeSubConfig| sub.vport == vport)
1286 {
1287 return Err(format!("invalid or duplicate RNode virtual port {}", vport));
1288 }
1289 let parse_u32 = |key: &str, default| {
1290 child
1291 .params
1292 .get(key)
1293 .and_then(|v| v.parse().ok())
1294 .unwrap_or(default)
1295 };
1296 let parse_u8_alias = |first: &str, second: &str, default| {
1297 child
1298 .params
1299 .get(first)
1300 .or_else(|| child.params.get(second))
1301 .and_then(|v| v.parse().ok())
1302 .unwrap_or(default)
1303 };
1304 let sub = RNodeSubConfig {
1305 name: child.name.clone(),
1306 vport,
1307 outgoing: child
1308 .params
1309 .get("outgoing")
1310 .and_then(|v| crate::config::parse_bool_pub(v))
1311 .unwrap_or(true),
1312 frequency: parse_u32("frequency", 868_000_000),
1313 bandwidth: parse_u32("bandwidth", 125_000),
1314 txpower: child
1315 .params
1316 .get("txpower")
1317 .and_then(|v| v.parse().ok())
1318 .unwrap_or(7),
1319 spreading_factor: parse_u8_alias("spreadingfactor", "spreading_factor", 8),
1320 coding_rate: parse_u8_alias("codingrate", "coding_rate", 5),
1321 flow_control: child
1322 .params
1323 .get("flow_control")
1324 .and_then(|v| crate::config::parse_bool_pub(v))
1325 .unwrap_or(false),
1326 st_alock: child
1327 .params
1328 .get("airtime_limit_short")
1329 .or_else(|| child.params.get("st_alock"))
1330 .and_then(|v| v.parse().ok()),
1331 lt_alock: child
1332 .params
1333 .get("airtime_limit_long")
1334 .or_else(|| child.params.get("lt_alock"))
1335 .and_then(|v| v.parse().ok()),
1336 };
1337 if let Some(error) = validate_sub_config(&sub) {
1338 return Err(error);
1339 }
1340 subinterfaces.push(sub);
1341 }
1342 if subinterfaces.is_empty() {
1343 return Err("RNodeMultiInterface requires at least one enabled subinterface".into());
1344 }
1345 config.multi = true;
1346 config.subinterfaces = subinterfaces;
1347 *lock_or_recover(&config.runtime, "rnode runtime") = RNodeRuntime::from_config(&config);
1348 Ok(Box::new(config))
1349 }
1350
1351 fn start(
1352 &self,
1353 config: Box<dyn InterfaceConfigData>,
1354 ctx: StartContext,
1355 ) -> io::Result<StartResult> {
1356 RNodeFactory.start(config, ctx)
1357 }
1358}
1359
1360pub(crate) fn rnode_runtime_handle_from_config(config: &RNodeConfig) -> RNodeRuntimeConfigHandle {
1361 RNodeRuntimeConfigHandle {
1362 interface_name: config.name.clone(),
1363 runtime: Arc::clone(&config.runtime),
1364 startup: RNodeRuntime::from_config(config),
1365 }
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370 use super::*;
1371 use crate::event;
1372 use crate::kiss;
1373 use crate::serial::open_pty_pair;
1374 use std::os::unix::io::{AsRawFd, FromRawFd};
1375 use std::path::PathBuf;
1376 use std::sync::mpsc::RecvTimeoutError;
1377 use tempfile::tempdir;
1378 fn poll_read(fd: i32, timeout_ms: i32) -> bool {
1380 let mut pfd = libc::pollfd {
1381 fd,
1382 events: libc::POLLIN,
1383 revents: 0,
1384 };
1385 let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
1386 ret > 0
1387 }
1388
1389 fn read_available(file: &mut Transport) -> Vec<u8> {
1391 let mut all = Vec::new();
1392 let mut buf = [0u8; 4096];
1393 while poll_read(file.as_raw_fd(), 100) {
1394 match file.read(&mut buf) {
1395 Ok(n) if n > 0 => all.extend_from_slice(&buf[..n]),
1396 _ => break,
1397 }
1398 }
1399 all
1400 }
1401
1402 fn slave_tty_path(fd: i32) -> PathBuf {
1403 let mut buf = [0u8; 256];
1404 let rc = unsafe { libc::ttyname_r(fd, buf.as_mut_ptr().cast(), buf.len()) };
1405 assert_eq!(rc, 0, "ttyname_r failed for fd {}", fd);
1406 let nul = buf.iter().position(|b| *b == 0).unwrap_or(buf.len());
1407 PathBuf::from(std::str::from_utf8(&buf[..nul]).unwrap())
1408 }
1409
1410 unsafe fn transport_from_raw_fd(fd: i32) -> Transport {
1411 Transport::Serial(std::fs::File::from_raw_fd(fd))
1412 }
1413
1414 fn wait_for_interface_event<F>(
1415 rx: &crate::event::EventReceiver,
1416 timeout: Duration,
1417 predicate: F,
1418 ) -> Event
1419 where
1420 F: Fn(&Event) -> bool,
1421 {
1422 let deadline = std::time::Instant::now() + timeout;
1423 loop {
1424 let remaining = deadline.saturating_duration_since(std::time::Instant::now());
1425 match rx.recv_timeout(remaining) {
1426 Ok(event) if predicate(&event) => return event,
1427 Ok(_) => continue,
1428 Err(RecvTimeoutError::Timeout) => panic!("timed out waiting for interface event"),
1429 Err(RecvTimeoutError::Disconnected) => panic!("event channel disconnected"),
1430 }
1431 }
1432 }
1433
1434 fn mock_respond_detect(master: &mut Transport) {
1436 master
1438 .write_all(&rnode_kiss::rnode_command(
1439 rnode_kiss::CMD_DETECT,
1440 &[rnode_kiss::DETECT_RESP],
1441 ))
1442 .unwrap();
1443 master
1445 .write_all(&rnode_kiss::rnode_command(
1446 rnode_kiss::CMD_FW_VERSION,
1447 &[0x01, 0x4A],
1448 ))
1449 .unwrap();
1450 master
1452 .write_all(&rnode_kiss::rnode_command(
1453 rnode_kiss::CMD_PLATFORM,
1454 &[0x80],
1455 ))
1456 .unwrap();
1457 master
1459 .write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_MCU, &[0x01]))
1460 .unwrap();
1461 master.flush().unwrap();
1462 }
1463
1464 #[test]
1465 fn rnode_detect_over_pty() {
1466 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1468 let mut master = unsafe { transport_from_raw_fd(master_fd) };
1469 let mut slave = unsafe { transport_from_raw_fd(slave_fd) };
1470
1471 mock_respond_detect(&mut master);
1473
1474 let mut decoder = rnode_kiss::RNodeDecoder::new();
1475 let mut events = Vec::new();
1476 let deadline = std::time::Instant::now() + Duration::from_secs(2);
1477
1478 while std::time::Instant::now() < deadline {
1479 let remaining = deadline.saturating_duration_since(std::time::Instant::now());
1480 let timeout_ms = remaining.as_millis().min(i32::MAX as u128) as i32;
1481 if !poll_read(slave.as_raw_fd(), timeout_ms) {
1482 break;
1483 }
1484
1485 let chunk = read_available(&mut slave);
1486 if chunk.is_empty() {
1487 continue;
1488 }
1489
1490 events.extend(decoder.feed(&chunk));
1491
1492 let saw_detect = events
1493 .iter()
1494 .any(|e| matches!(e, rnode_kiss::RNodeEvent::Detected(true)));
1495 let saw_firmware = events
1496 .iter()
1497 .any(|e| matches!(e, rnode_kiss::RNodeEvent::FirmwareVersion { .. }));
1498 if saw_detect && saw_firmware {
1499 break;
1500 }
1501 }
1502
1503 assert!(
1504 events
1505 .iter()
1506 .any(|e| matches!(e, rnode_kiss::RNodeEvent::Detected(true))),
1507 "should detect device"
1508 );
1509 assert!(
1510 events
1511 .iter()
1512 .any(|e| matches!(e, rnode_kiss::RNodeEvent::FirmwareVersion { .. })),
1513 "should get firmware version"
1514 );
1515 }
1516
1517 #[test]
1518 fn rnode_configure_commands() {
1519 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1520 let mut master = unsafe { transport_from_raw_fd(master_fd) };
1521 let writer_file = unsafe { transport_from_raw_fd(libc::dup(slave_fd)) };
1522 let writer = Arc::new(Mutex::new(DeviceWriter::new(writer_file)));
1523
1524 let sub = RNodeSubConfig {
1525 name: "test".into(),
1526 vport: 0,
1527 outgoing: true,
1528 frequency: 868_000_000,
1529 bandwidth: 125_000,
1530 txpower: 7,
1531 spreading_factor: 8,
1532 coding_rate: 5,
1533 flow_control: false,
1534 st_alock: None,
1535 lt_alock: None,
1536 };
1537
1538 configure_subinterface(&writer, 0, &sub, false).unwrap();
1539
1540 let data = read_available(&mut master);
1542
1543 assert!(
1545 data.windows(2)
1546 .any(|w| w[0] == kiss::FEND && w[1] == rnode_kiss::CMD_FREQUENCY),
1547 "should contain FREQUENCY command"
1548 );
1549 assert!(
1551 data.windows(2)
1552 .any(|w| w[0] == kiss::FEND && w[1] == rnode_kiss::CMD_BANDWIDTH),
1553 "should contain BANDWIDTH command"
1554 );
1555 assert!(
1557 data.windows(3).any(|w| w[0] == kiss::FEND
1558 && w[1] == rnode_kiss::CMD_RADIO_STATE
1559 && w[2] == rnode_kiss::RADIO_STATE_ON),
1560 "should contain RADIO_STATE ON command"
1561 );
1562
1563 unsafe { libc::close(slave_fd) };
1564 }
1565
1566 #[test]
1567 fn rnode_data_roundtrip() {
1568 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1569 let mut master = unsafe { transport_from_raw_fd(master_fd) };
1570 let slave = unsafe { transport_from_raw_fd(slave_fd) };
1571
1572 let payload = vec![0x01, 0x02, 0x03, 0x04, 0x05];
1574 let frame = rnode_kiss::rnode_data_frame(0, &payload);
1575 master.write_all(&frame).unwrap();
1576 master.flush().unwrap();
1577
1578 assert!(poll_read(slave.as_raw_fd(), 2000));
1580 let mut decoder = rnode_kiss::RNodeDecoder::new();
1581 let mut buf = [0u8; 4096];
1582 let mut slave_file = slave;
1583 let n = slave_file.read(&mut buf).unwrap();
1584 let events = decoder.feed(&buf[..n]);
1585
1586 assert_eq!(events.len(), 1);
1587 match &events[0] {
1588 rnode_kiss::RNodeEvent::DataFrame { index, data } => {
1589 assert_eq!(*index, 0);
1590 assert_eq!(data, &payload);
1591 }
1592 other => panic!("expected DataFrame, got {:?}", other),
1593 }
1594 }
1595
1596 #[test]
1597 fn rnode_flow_control() {
1598 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1599 let writer_file = unsafe { transport_from_raw_fd(slave_fd) };
1600 let shared_writer = Arc::new(Mutex::new(DeviceWriter::new(writer_file)));
1601
1602 let flow_state = Arc::new(Mutex::new(SubFlowState {
1603 ready: true,
1604 queue: std::collections::VecDeque::new(),
1605 }));
1606
1607 let mut sub_writer = RNodeSubWriter {
1608 confirmed_pending: None,
1609 writer: shared_writer.clone(),
1610 index: 0,
1611 flow_control: true,
1612 flow_state: flow_state.clone(),
1613 multi: false,
1614 };
1615
1616 sub_writer.send_frame(b"hello").unwrap();
1618 assert!(!flow_state.lock().unwrap().ready);
1619
1620 sub_writer.send_frame(b"world").unwrap();
1622 assert_eq!(flow_state.lock().unwrap().queue.len(), 1);
1623
1624 process_flow_queue(&flow_state, &shared_writer, 0, false);
1626 assert_eq!(flow_state.lock().unwrap().queue.len(), 0);
1627 assert!(!flow_state.lock().unwrap().ready); process_flow_queue(&flow_state, &shared_writer, 0, false);
1631 assert!(flow_state.lock().unwrap().ready);
1632
1633 unsafe { libc::close(master_fd) };
1634 }
1635
1636 #[test]
1637 fn signal_quality_matches_python_mapping_and_clamps() {
1638 assert_eq!(signal_quality(8, -11.0), 0.0);
1639 assert_eq!(signal_quality(8, -2.5), 50.0);
1640 assert_eq!(signal_quality(8, 6.0), 100.0);
1641 assert_eq!(signal_quality(8, 30.0), 100.0);
1642 assert_eq!(signal_quality(8, -40.0), 0.0);
1643 assert_eq!(signal_quality(10, -2.5), 59.5);
1644 }
1645
1646 #[test]
1647 fn rnode_reset_clears_pending_rx_metadata() {
1648 let mut last_rssi = Some(-101);
1649 let mut last_snr = Some(7.25);
1650
1651 clear_pending_rx_metadata(&mut last_rssi, &mut last_snr);
1652
1653 assert_eq!(last_rssi, None);
1654 assert_eq!(last_snr, None);
1655 }
1656
1657 #[test]
1658 fn confirmed_rnode_frame_waits_for_ready_and_flushes_once() {
1659 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1660 let mut master = unsafe { transport_from_raw_fd(master_fd) };
1661 let state = Arc::new(Mutex::new(SubFlowState {
1662 ready: false,
1663 queue: std::collections::VecDeque::new(),
1664 }));
1665 let mut writer = RNodeSubWriter {
1666 confirmed_pending: None,
1667 writer: Arc::new(Mutex::new(DeviceWriter::new(unsafe {
1668 transport_from_raw_fd(slave_fd)
1669 }))),
1670 index: 0,
1671 flow_control: true,
1672 flow_state: state.clone(),
1673 multi: false,
1674 };
1675 assert_eq!(
1676 writer
1677 .send_frame_confirmed(b"confirmed")
1678 .unwrap_err()
1679 .kind(),
1680 io::ErrorKind::WouldBlock
1681 );
1682 assert!(state.lock().unwrap().queue.is_empty());
1683 assert!(!poll_read(master.as_raw_fd(), 20));
1684 state.lock().unwrap().ready = true;
1685 Writer::flush(&mut writer).unwrap();
1686 let expected = rnode_kiss::rnode_data_frame(0, b"confirmed");
1687 let mut actual = vec![0; expected.len()];
1688 master.read_exact(&mut actual).unwrap();
1689 assert_eq!(actual, expected);
1690 Writer::flush(&mut writer).unwrap();
1691 assert!(!poll_read(master.as_raw_fd(), 20));
1692 }
1693
1694 #[test]
1695 fn rnode_snr_metadata_does_not_depend_on_rssi() {
1696 let mut last_rssi = None;
1697 let mut last_snr = None;
1698
1699 assert!(update_pending_rx_metadata(
1700 &rnode_kiss::RNodeEvent::StatSnr(-12),
1701 &mut last_rssi,
1702 &mut last_snr,
1703 ));
1704
1705 assert_eq!(last_rssi, None);
1706 assert_eq!(last_snr, Some(-3.0));
1707 }
1708
1709 #[test]
1710 fn rnode_rx_metadata_reports_rssi_and_snr() {
1711 let mut last_rssi = None;
1712 let mut last_snr = None;
1713
1714 assert!(update_pending_rx_metadata(
1715 &rnode_kiss::RNodeEvent::StatRssi(57),
1716 &mut last_rssi,
1717 &mut last_snr,
1718 ));
1719 assert!(update_pending_rx_metadata(
1720 &rnode_kiss::RNodeEvent::StatSnr(-12),
1721 &mut last_rssi,
1722 &mut last_snr,
1723 ));
1724
1725 assert_eq!(last_rssi, Some(-100));
1726 assert_eq!(last_snr, Some(-3.0));
1727 }
1728
1729 #[test]
1730 fn rnode_sub_writer_format() {
1731 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1732 let mut master = unsafe { transport_from_raw_fd(master_fd) };
1733 let writer_file = unsafe { transport_from_raw_fd(slave_fd) };
1734 let shared_writer = Arc::new(Mutex::new(DeviceWriter::new(writer_file)));
1735
1736 let flow_state = Arc::new(Mutex::new(SubFlowState {
1737 ready: true,
1738 queue: std::collections::VecDeque::new(),
1739 }));
1740
1741 let mut sub_writer = RNodeSubWriter {
1742 confirmed_pending: None,
1743 writer: shared_writer,
1744 index: 1, flow_control: false,
1746 flow_state,
1747 multi: false,
1748 };
1749
1750 let payload = vec![0xAA, 0xBB, 0xCC];
1751 sub_writer.send_frame(&payload).unwrap();
1752
1753 assert!(poll_read(master.as_raw_fd(), 2000));
1755 let mut buf = [0u8; 256];
1756 let n = master.read(&mut buf).unwrap();
1757
1758 assert_eq!(buf[0], kiss::FEND);
1760 assert_eq!(buf[1], 0x10); assert_eq!(buf[n - 1], kiss::FEND);
1762 }
1763
1764 #[test]
1765 fn rnode_multi_sub_routing() {
1766 let mut decoder = rnode_kiss::RNodeDecoder::new();
1768
1769 let payload0 = vec![0x01, 0x02];
1770 let frame0 = rnode_kiss::rnode_data_frame(0, &payload0);
1771 let events0 = decoder.feed(&frame0);
1772 assert_eq!(events0.len(), 1);
1773 assert_eq!(
1774 events0[0],
1775 rnode_kiss::RNodeEvent::DataFrame {
1776 index: 0,
1777 data: payload0
1778 }
1779 );
1780
1781 let payload1 = vec![0x03, 0x04];
1782 let frame1 = rnode_kiss::rnode_data_frame(1, &payload1);
1783 let events1 = decoder.feed(&frame1);
1784 assert_eq!(events1.len(), 1);
1785 assert_eq!(
1786 events1[0],
1787 rnode_kiss::RNodeEvent::DataFrame {
1788 index: 1,
1789 data: payload1
1790 }
1791 );
1792 }
1793
1794 #[test]
1795 fn rnode_error_handling() {
1796 let mut decoder = rnode_kiss::RNodeDecoder::new();
1804 let frame = rnode_kiss::rnode_command(rnode_kiss::CMD_ERROR, &[0x02]);
1805 let events = decoder.feed(&frame);
1806 assert_eq!(events.len(), 1);
1807 assert_eq!(
1809 events[0],
1810 rnode_kiss::RNodeEvent::DataFrame {
1811 index: 5,
1812 data: vec![0x02]
1813 }
1814 );
1815 }
1816
1817 #[test]
1818 fn rnode_config_validation() {
1819 let good = RNodeSubConfig {
1820 name: "test".into(),
1821 vport: 0,
1822 outgoing: true,
1823 frequency: 868_000_000,
1824 bandwidth: 125_000,
1825 txpower: 7,
1826 spreading_factor: 8,
1827 coding_rate: 5,
1828 flow_control: false,
1829 st_alock: None,
1830 lt_alock: None,
1831 };
1832 assert!(validate_sub_config(&good).is_none());
1833
1834 let mut bad = good.clone();
1836 bad.frequency = 100;
1837 assert!(validate_sub_config(&bad).is_some());
1838
1839 bad = good.clone();
1841 bad.spreading_factor = 13;
1842 assert!(validate_sub_config(&bad).is_some());
1843
1844 bad = good.clone();
1846 bad.coding_rate = 9;
1847 assert!(validate_sub_config(&bad).is_some());
1848
1849 bad = good.clone();
1851 bad.bandwidth = 5;
1852 assert!(validate_sub_config(&bad).is_some());
1853
1854 bad = good.clone();
1856 bad.txpower = 50;
1857 assert!(validate_sub_config(&bad).is_some());
1858 }
1859
1860 #[test]
1861 fn multi_factory_preserves_nonsequential_virtual_ports_and_disabled_children() {
1862 let parent = HashMap::from([
1863 ("port".to_string(), "/dev/ttyUSB0".to_string()),
1864 ("speed".to_string(), "115200".to_string()),
1865 ]);
1866 let children = vec![
1867 crate::config::ParsedSubinterface {
1868 name: "disabled".into(),
1869 params: HashMap::from([
1870 ("interface_enabled".into(), "no".into()),
1871 ("vport".into(), "2".into()),
1872 ]),
1873 },
1874 crate::config::ParsedSubinterface {
1875 name: "high".into(),
1876 params: HashMap::from([
1877 ("interface_enabled".into(), "yes".into()),
1878 ("vport".into(), "7".into()),
1879 ("frequency".into(), "915000000".into()),
1880 ("airtime_limit_short".into(), "12.5".into()),
1881 ("outgoing".into(), "no".into()),
1882 ]),
1883 },
1884 crate::config::ParsedSubinterface {
1885 name: "low".into(),
1886 params: HashMap::from([
1887 ("enabled".into(), "yes".into()),
1888 ("vport".into(), "1".into()),
1889 ]),
1890 },
1891 ];
1892 let parsed = RNodeMultiFactory
1893 .parse_config_section(
1894 "multi",
1895 InterfaceId(40),
1896 ConfigSection {
1897 params: &parent,
1898 children: &children,
1899 },
1900 )
1901 .unwrap();
1902 let config = parsed.into_any().downcast::<RNodeConfig>().unwrap();
1903 assert_eq!(config.subinterfaces.len(), 2);
1904 assert_eq!(config.subinterfaces[0].vport, 7);
1905 assert_eq!(config.subinterfaces[1].vport, 1);
1906 assert!(!config.subinterfaces[0].outgoing);
1907 assert_eq!(config.subinterfaces[0].st_alock, Some(12.5));
1908 }
1909
1910 #[test]
1911 fn rnode_lora_bitrate_estimate_uses_radio_params() {
1912 let mut sub = RNodeSubConfig {
1913 name: "test".into(),
1914 vport: 0,
1915 outgoing: true,
1916 frequency: 868_000_000,
1917 bandwidth: 125_000,
1918 txpower: 7,
1919 spreading_factor: 8,
1920 coding_rate: 5,
1921 flow_control: false,
1922 st_alock: None,
1923 lt_alock: None,
1924 };
1925
1926 assert_eq!(estimate_lora_bitrate_bps(&sub), 3125);
1927
1928 sub.spreading_factor = 12;
1929 assert_eq!(estimate_lora_bitrate_bps(&sub), 293);
1930
1931 sub.bandwidth = 250_000;
1932 assert_eq!(estimate_lora_bitrate_bps(&sub), 586);
1933 }
1934
1935 #[test]
1936 fn rnode_lora_airtime_profile_uses_radio_params() {
1937 let sub = RNodeSubConfig {
1938 name: "test".into(),
1939 vport: 0,
1940 outgoing: true,
1941 frequency: 868_000_000,
1942 bandwidth: 125_000,
1943 txpower: 7,
1944 spreading_factor: 8,
1945 coding_rate: 5,
1946 flow_control: false,
1947 st_alock: None,
1948 lt_alock: None,
1949 };
1950
1951 let profile = lora_airtime_profile(&sub);
1952 assert!((profile.transmit_time_secs(100) - 0.307712).abs() < 0.000001);
1953 assert_eq!(
1954 profile,
1955 AirtimeProfile::Lora {
1956 bandwidth: 125_000,
1957 spreading_factor: 8,
1958 coding_rate: 5,
1959 preamble_symbols: LORA_PREAMBLE_SYMBOLS,
1960 explicit_header: LORA_EXPLICIT_HEADER,
1961 crc: LORA_CRC,
1962 }
1963 );
1964 }
1965
1966 fn thread_named(prefix: &str) -> bool {
1967 std::fs::read_dir("/proc/self/task").unwrap().any(|task| {
1968 let comm = task.unwrap().path().join("comm");
1969 std::fs::read_to_string(comm).is_ok_and(|comm| comm.trim().starts_with(prefix))
1970 })
1971 }
1972
1973 fn device_reads(master: &mut Transport, want: usize, timeout_ms: i32) -> Vec<Vec<u8>> {
1976 let mut decoder = rnode_kiss::RNodeDecoder::new();
1977 let mut frames = Vec::new();
1978 let mut buf = [0u8; 1024];
1979 while frames.len() < want && poll_read(master.as_raw_fd(), timeout_ms) {
1980 let n = master.read(&mut buf).unwrap();
1981 for event in decoder.feed(&buf[..n]) {
1982 if let rnode_kiss::RNodeEvent::DataFrame { data, .. } = event {
1983 frames.push(data);
1984 }
1985 }
1986 }
1987 frames
1988 }
1989
1990 #[test]
1991 fn folded_rnode_runs_on_the_driver_loop() {
1992 if !crate::engine::FOLD_IO {
1993 return;
1994 }
1995 let tempdir = tempdir().unwrap();
1996 let port_path = tempdir.path().join("rnode-port");
1997 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1998 std::os::unix::fs::symlink(slave_tty_path(slave_fd), &port_path).unwrap();
1999 let mut master = unsafe { transport_from_raw_fd(master_fd) };
2000 let slave = unsafe { transport_from_raw_fd(slave_fd) };
2001
2002 let (tx, rx) = event::channel();
2003 let mut config = RNodeConfig {
2004 name: "folded-rnode".into(),
2005 port: port_path.display().to_string(),
2006 subinterfaces: vec![RNodeSubConfig {
2007 name: "folded-rnode".into(),
2008 vport: 0,
2009 outgoing: true,
2010 frequency: 868_000_000,
2011 bandwidth: 125_000,
2012 txpower: 7,
2013 spreading_factor: 8,
2014 coding_rate: 5,
2015 flow_control: true,
2016 st_alock: None,
2017 lt_alock: None,
2018 }],
2019 base_interface_id: InterfaceId(43),
2020 ..Default::default()
2021 };
2022 config.runtime = Arc::new(Mutex::new(RNodeRuntime::from_config(&config)));
2023 let (mut writers, control) = start_with_control(config, tx.clone()).unwrap();
2024 let (_, writer) = writers.remove(0);
2025 assert!(writer.sends_inline());
2026 let (mut writer, _) =
2027 crate::interface::wrap_async_writer(writer, InterfaceId(43), "rnode", tx, 16);
2028
2029 thread::sleep(Duration::from_millis(2500));
2030 mock_respond_detect(&mut master);
2031 wait_for_interface_event(&rx, Duration::from_secs(4), |event| {
2032 matches!(event, Event::InterfaceUp(InterfaceId(43), None, None))
2033 });
2034 thread::sleep(Duration::from_millis(100));
2036 assert!(!thread_named("rnode-setup-43"));
2037 assert!(!thread_named("rnode-reader-43"));
2038 assert!(!thread_named("rnode-keepaliv"));
2039 assert!(!thread_named("iface-writer-43"));
2040
2041 master
2043 .write_all(&rnode_kiss::rnode_command(
2044 rnode_kiss::CMD_STAT_RSSI,
2045 &[100],
2046 ))
2047 .unwrap();
2048 let payload: Vec<u8> = (0..32).collect();
2049 master
2050 .write_all(&rnode_kiss::rnode_data_frame(0, &payload))
2051 .unwrap();
2052 match wait_for_interface_event(&rx, Duration::from_secs(2), |event| {
2053 matches!(event, Event::Frame { .. })
2054 }) {
2055 Event::Frame {
2056 interface_id,
2057 data,
2058 rssi,
2059 ..
2060 } => {
2061 assert_eq!(interface_id, InterfaceId(43));
2062 assert_eq!(&*data, &payload[..]);
2063 assert_eq!(rssi, Some(-57));
2064 }
2065 _ => unreachable!(),
2066 }
2067
2068 writer.send_frame(b"first frame").unwrap();
2070 writer.send_frame(b"second frame").unwrap();
2071 writer.flush_pending();
2072 assert_eq!(
2073 device_reads(&mut master, 2, 200),
2074 vec![b"first frame".to_vec()]
2075 );
2076 master
2077 .write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_READY, &[0x01]))
2078 .unwrap();
2079 assert!(rx.recv_timeout(Duration::from_millis(200)).is_err());
2080 assert_eq!(
2081 device_reads(&mut master, 1, 2000),
2082 vec![b"second frame".to_vec()]
2083 );
2084
2085 drop(master);
2087 drop(slave);
2088 wait_for_interface_event(&rx, Duration::from_secs(2), |event| {
2089 matches!(event, Event::InterfaceDown(InterfaceId(43)))
2090 });
2091 control.request_stop();
2092 }
2093
2094 #[test]
2095 fn rnode_reconnects_after_serial_disconnect() {
2096 let tempdir = tempdir().unwrap();
2097 let port_path = tempdir.path().join("rnode-port");
2098
2099 let (master1_fd, slave1_fd) = open_pty_pair().unwrap();
2100 let slave1_path = slave_tty_path(slave1_fd);
2101 std::os::unix::fs::symlink(&slave1_path, &port_path).unwrap();
2102
2103 let mut master1 = unsafe { transport_from_raw_fd(master1_fd) };
2104 let slave1 = unsafe { transport_from_raw_fd(slave1_fd) };
2105
2106 let (tx, rx) = event::channel();
2107 let sub = RNodeSubConfig {
2108 name: "test-rnode".into(),
2109 vport: 0,
2110 outgoing: true,
2111 frequency: 868_000_000,
2112 bandwidth: 125_000,
2113 txpower: 7,
2114 spreading_factor: 8,
2115 coding_rate: 5,
2116 flow_control: false,
2117 st_alock: None,
2118 lt_alock: None,
2119 };
2120 let mut config = RNodeConfig {
2121 name: "test-rnode".into(),
2122 port: port_path.display().to_string(),
2123 speed: 115200,
2124 subinterfaces: vec![sub],
2125 multi: false,
2126 id_interval: None,
2127 id_callsign: None,
2128 base_interface_id: InterfaceId(41),
2129 pre_opened_fd: None,
2130 underlay_mark: None,
2131 runtime: Arc::new(Mutex::new(RNodeRuntime {
2132 sub: RNodeSubConfig {
2133 name: String::new(),
2134 vport: 0,
2135 outgoing: true,
2136 frequency: 868_000_000,
2137 bandwidth: 125_000,
2138 txpower: 7,
2139 spreading_factor: 8,
2140 coding_rate: 5,
2141 flow_control: false,
2142 st_alock: None,
2143 lt_alock: None,
2144 },
2145 writer: None,
2146 })),
2147 };
2148 config.runtime = Arc::new(Mutex::new(RNodeRuntime::from_config(&config)));
2149
2150 let (_writers, control) = start_with_control(config, tx).unwrap();
2151
2152 thread::sleep(Duration::from_secs(3));
2153 mock_respond_detect(&mut master1);
2154 let up = wait_for_interface_event(&rx, Duration::from_secs(4), |event| {
2155 matches!(event, Event::InterfaceUp(InterfaceId(41), _, _))
2156 });
2157 assert!(matches!(
2158 up,
2159 Event::InterfaceUp(InterfaceId(41), None, None)
2160 ));
2161
2162 drop(master1);
2163 drop(slave1);
2164
2165 let down = wait_for_interface_event(&rx, Duration::from_secs(4), |event| {
2166 matches!(event, Event::InterfaceDown(InterfaceId(41)))
2167 });
2168 assert!(matches!(down, Event::InterfaceDown(InterfaceId(41))));
2169
2170 let (master2_fd, slave2_fd) = open_pty_pair().unwrap();
2171 let slave2_path = slave_tty_path(slave2_fd);
2172 std::fs::remove_file(&port_path).unwrap();
2173 std::os::unix::fs::symlink(&slave2_path, &port_path).unwrap();
2174
2175 let mut master2 = unsafe { transport_from_raw_fd(master2_fd) };
2176 let _slave2 = unsafe { transport_from_raw_fd(slave2_fd) };
2177
2178 thread::sleep(Duration::from_secs(3));
2179 mock_respond_detect(&mut master2);
2180 let up = wait_for_interface_event(&rx, Duration::from_secs(4), |event| {
2181 matches!(event, Event::InterfaceUp(InterfaceId(41), _, _))
2182 });
2183 assert!(matches!(
2184 up,
2185 Event::InterfaceUp(InterfaceId(41), Some(_), None)
2186 ));
2187
2188 let telemetry = rx.recv_timeout(Duration::from_secs(1)).unwrap();
2191 assert!(matches!(
2192 telemetry,
2193 Event::InterfaceTelemetry {
2194 interface_id: InterfaceId(41),
2195 ..
2196 }
2197 ));
2198
2199 control.request_stop();
2200 assert!(control.should_stop());
2201 drop(master2);
2204 assert!(rx.recv_timeout(Duration::from_millis(500)).is_err());
2205 }
2206}