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