1use std::io::{self, Read, Write};
10use std::sync::{Arc, Mutex};
11use std::thread;
12use std::time::Duration;
13
14use rns_core::transport::types::InterfaceId;
15
16use crate::event::{Event, EventSender};
17use crate::interface::Writer;
18use crate::rnode_kiss;
19use crate::serial::{Parity, SerialConfig, SerialPort};
20
21pub const FREQ_MIN: u32 = 137_000_000;
23pub const FREQ_MAX: u32 = 3_000_000_000;
24pub const BW_MIN: u32 = 7_800;
25pub const BW_MAX: u32 = 1_625_000;
26pub const SF_MIN: u8 = 5;
27pub const SF_MAX: u8 = 12;
28pub const CR_MIN: u8 = 5;
29pub const CR_MAX: u8 = 8;
30pub const TXPOWER_MIN: i8 = 0;
31pub const TXPOWER_MAX: i8 = 37;
32pub const HW_MTU: u32 = 508;
33
34#[derive(Debug, Clone)]
36pub struct RNodeSubConfig {
37 pub name: String,
38 pub frequency: u32,
39 pub bandwidth: u32,
40 pub txpower: i8,
41 pub spreading_factor: u8,
42 pub coding_rate: u8,
43 pub flow_control: bool,
44 pub st_alock: Option<f32>,
45 pub lt_alock: Option<f32>,
46}
47
48#[derive(Debug, Clone)]
50pub struct RNodeConfig {
51 pub name: String,
52 pub port: String,
53 pub speed: u32,
54 pub subinterfaces: Vec<RNodeSubConfig>,
55 pub id_interval: Option<u32>,
56 pub id_callsign: Option<Vec<u8>>,
57 pub base_interface_id: InterfaceId,
58 pub pre_opened_fd: Option<i32>,
61}
62
63impl Default for RNodeConfig {
64 fn default() -> Self {
65 RNodeConfig {
66 name: String::new(),
67 port: String::new(),
68 speed: 115200,
69 subinterfaces: Vec::new(),
70 id_interval: None,
71 id_callsign: None,
72 base_interface_id: InterfaceId(0),
73 pre_opened_fd: None,
74 }
75 }
76}
77
78pub fn validate_sub_config(sub: &RNodeSubConfig) -> Option<String> {
80 if sub.frequency < FREQ_MIN || sub.frequency > FREQ_MAX {
81 return Some(format!("Invalid frequency {} for {}", sub.frequency, sub.name));
82 }
83 if sub.bandwidth < BW_MIN || sub.bandwidth > BW_MAX {
84 return Some(format!("Invalid bandwidth {} for {}", sub.bandwidth, sub.name));
85 }
86 if sub.spreading_factor < SF_MIN || sub.spreading_factor > SF_MAX {
87 return Some(format!("Invalid SF {} for {}", sub.spreading_factor, sub.name));
88 }
89 if sub.coding_rate < CR_MIN || sub.coding_rate > CR_MAX {
90 return Some(format!("Invalid CR {} for {}", sub.coding_rate, sub.name));
91 }
92 if sub.txpower < TXPOWER_MIN || sub.txpower > TXPOWER_MAX {
93 return Some(format!("Invalid TX power {} for {}", sub.txpower, sub.name));
94 }
95 if let Some(st) = sub.st_alock {
96 if st < 0.0 || st > 100.0 {
97 return Some(format!("Invalid ST airtime limit {} for {}", st, sub.name));
98 }
99 }
100 if let Some(lt) = sub.lt_alock {
101 if lt < 0.0 || lt > 100.0 {
102 return Some(format!("Invalid LT airtime limit {} for {}", lt, sub.name));
103 }
104 }
105 None
106}
107
108struct RNodeSubWriter {
111 writer: Arc<Mutex<std::fs::File>>,
112 index: u8,
113 flow_control: bool,
114 flow_state: Arc<Mutex<SubFlowState>>,
115}
116
117struct SubFlowState {
118 ready: bool,
119 queue: std::collections::VecDeque<Vec<u8>>,
120}
121
122impl Writer for RNodeSubWriter {
123 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
124 let frame = rnode_kiss::rnode_data_frame(self.index, data);
125 if self.flow_control {
126 let mut state = self.flow_state.lock().unwrap();
127 if state.ready {
128 state.ready = false;
129 drop(state);
130 self.writer.lock().unwrap().write_all(&frame)
131 } else {
132 state.queue.push_back(data.to_vec());
133 Ok(())
134 }
135 } else {
136 self.writer.lock().unwrap().write_all(&frame)
137 }
138 }
139}
140
141pub fn start(
148 config: RNodeConfig,
149 tx: EventSender,
150) -> io::Result<Vec<(InterfaceId, Box<dyn Writer>)>> {
151 for sub in &config.subinterfaces {
153 if let Some(err) = validate_sub_config(sub) {
154 return Err(io::Error::new(io::ErrorKind::InvalidInput, err));
155 }
156 }
157
158 if config.subinterfaces.is_empty() {
159 return Err(io::Error::new(
160 io::ErrorKind::InvalidInput,
161 "No subinterfaces configured",
162 ));
163 }
164
165 let (reader_file, shared_writer) = if let Some(fd) = config.pre_opened_fd {
166 let port = SerialPort::from_raw_fd(fd);
168 let r = port.reader()?;
169 let w = port.writer()?;
170 std::mem::forget(port); (r, Arc::new(Mutex::new(w)))
172 } else {
173 let serial_config = SerialConfig {
174 path: config.port.clone(),
175 baud: config.speed,
176 data_bits: 8,
177 parity: Parity::None,
178 stop_bits: 1,
179 };
180 let port = SerialPort::open(&serial_config)?;
181 let r = port.reader()?;
182 let w = port.writer()?;
183 (r, Arc::new(Mutex::new(w)))
184 };
185
186 let num_subs = config.subinterfaces.len();
188 let mut writers: Vec<(InterfaceId, Box<dyn Writer>)> = Vec::with_capacity(num_subs);
189 let mut flow_states: Vec<Arc<Mutex<SubFlowState>>> = Vec::with_capacity(num_subs);
190
191 for (i, sub) in config.subinterfaces.iter().enumerate() {
192 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
193 let flow_state = Arc::new(Mutex::new(SubFlowState {
194 ready: true,
195 queue: std::collections::VecDeque::new(),
196 }));
197 flow_states.push(flow_state.clone());
198
199 let sub_writer: Box<dyn Writer> = Box::new(RNodeSubWriter {
200 writer: shared_writer.clone(),
201 index: i as u8,
202 flow_control: sub.flow_control,
203 flow_state,
204 });
205 writers.push((sub_id, sub_writer));
206 }
207
208 let reader_shared_writer = shared_writer.clone();
210 let reader_config = config.clone();
211 let reader_flow_states = flow_states;
212 thread::Builder::new()
213 .name(format!("rnode-reader-{}", config.base_interface_id.0))
214 .spawn(move || {
215 reader_loop(
216 reader_file,
217 reader_shared_writer,
218 reader_config,
219 tx,
220 reader_flow_states,
221 );
222 })?;
223
224 let keepalive_writer = shared_writer.clone();
227 let keepalive_name = config.name.clone();
228 thread::Builder::new()
229 .name(format!("rnode-keepalive-{}", config.base_interface_id.0))
230 .spawn(move || {
231 let detect = rnode_kiss::detect_request();
232 loop {
233 thread::sleep(Duration::from_secs(15));
234 if let Err(e) = keepalive_writer.lock().unwrap().write_all(&detect) {
235 log::debug!("[{}] keepalive write failed: {}", keepalive_name, e);
236 return;
237 }
238 }
239 })?;
240
241 Ok(writers)
242}
243
244fn reader_loop(
246 mut reader: std::fs::File,
247 writer: Arc<Mutex<std::fs::File>>,
248 config: RNodeConfig,
249 tx: EventSender,
250 flow_states: Vec<Arc<Mutex<SubFlowState>>>,
251) {
252 thread::sleep(Duration::from_secs(2));
254
255 {
257 let detect_cmd = rnode_kiss::detect_request();
258 let mut cmd = detect_cmd;
260 cmd.extend_from_slice(&rnode_kiss::rnode_command(rnode_kiss::CMD_FW_VERSION, &[0x00]));
261 cmd.extend_from_slice(&rnode_kiss::rnode_command(rnode_kiss::CMD_PLATFORM, &[0x00]));
262 cmd.extend_from_slice(&rnode_kiss::rnode_command(rnode_kiss::CMD_MCU, &[0x00]));
263
264 if let Err(e) = writer.lock().unwrap().write_all(&cmd) {
265 log::error!("[{}] failed to send detect: {}", config.name, e);
266 return;
267 }
268 }
269
270 let mut decoder = rnode_kiss::RNodeDecoder::new();
271 let mut buf = [0u8; 4096];
272 let mut detected = false;
273
274 let detect_start = std::time::Instant::now();
276 let detect_timeout = Duration::from_secs(5);
277
278 while !detected && detect_start.elapsed() < detect_timeout {
279 match reader.read(&mut buf) {
280 Ok(0) => {
281 log::warn!("[{}] serial port closed during detect", config.name);
282 return;
283 }
284 Ok(n) => {
285 for event in decoder.feed(&buf[..n]) {
286 match event {
287 rnode_kiss::RNodeEvent::Detected(true) => {
288 detected = true;
289 log::info!("[{}] RNode device detected", config.name);
290 }
291 rnode_kiss::RNodeEvent::FirmwareVersion { major, minor } => {
292 log::info!(
293 "[{}] firmware version {}.{}",
294 config.name,
295 major,
296 minor
297 );
298 }
299 rnode_kiss::RNodeEvent::Platform(p) => {
300 log::info!("[{}] platform: 0x{:02X}", config.name, p);
301 }
302 rnode_kiss::RNodeEvent::Mcu(m) => {
303 log::info!("[{}] MCU: 0x{:02X}", config.name, m);
304 }
305 _ => {}
306 }
307 }
308 }
309 Err(e) => {
310 log::error!("[{}] serial read error during detect: {}", config.name, e);
311 return;
312 }
313 }
314 }
315
316 if !detected {
317 log::error!("[{}] RNode detection timed out", config.name);
318 return;
319 }
320
321 for (i, sub) in config.subinterfaces.iter().enumerate() {
323 if let Err(e) = configure_subinterface(&writer, i as u8, sub, config.subinterfaces.len() > 1) {
324 log::error!("[{}] failed to configure subinterface {}: {}", config.name, i, e);
325 return;
326 }
327 }
328
329 thread::sleep(Duration::from_millis(300));
331
332 for i in 0..config.subinterfaces.len() {
334 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
335 let _ = tx.send(Event::InterfaceUp(sub_id, None, None));
336 }
337
338 log::info!(
339 "[{}] RNode configured with {} subinterface(s)",
340 config.name,
341 config.subinterfaces.len()
342 );
343
344 loop {
346 match reader.read(&mut buf) {
347 Ok(0) => {
348 log::warn!("[{}] serial port closed", config.name);
349 for i in 0..config.subinterfaces.len() {
350 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
351 let _ = tx.send(Event::InterfaceDown(sub_id));
352 }
353 return;
355 }
356 Ok(n) => {
357 for event in decoder.feed(&buf[..n]) {
358 match event {
359 rnode_kiss::RNodeEvent::DataFrame { index, data } => {
360 let sub_id = InterfaceId(config.base_interface_id.0 + index as u64);
361 if tx
362 .send(Event::Frame {
363 interface_id: sub_id,
364 data,
365 })
366 .is_err()
367 {
368 return;
369 }
370 }
371 rnode_kiss::RNodeEvent::Ready => {
372 for (i, fs) in flow_states.iter().enumerate() {
374 if config.subinterfaces[i].flow_control {
375 process_flow_queue(fs, &writer, i as u8);
376 }
377 }
378 }
379 rnode_kiss::RNodeEvent::Error(code) => {
380 log::error!("[{}] RNode error: 0x{:02X}", config.name, code);
381 }
382 _ => {
383 }
385 }
386 }
387 }
388 Err(e) => {
389 log::error!("[{}] serial read error: {}", config.name, e);
390 for i in 0..config.subinterfaces.len() {
391 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
392 let _ = tx.send(Event::InterfaceDown(sub_id));
393 }
394 return;
395 }
396 }
397 }
398}
399
400fn configure_subinterface(
402 writer: &Arc<Mutex<std::fs::File>>,
403 index: u8,
404 sub: &RNodeSubConfig,
405 multi: bool,
406) -> io::Result<()> {
407 let mut w = writer.lock().unwrap();
408
409 let freq_bytes = [
411 (sub.frequency >> 24) as u8,
412 (sub.frequency >> 16) as u8,
413 (sub.frequency >> 8) as u8,
414 (sub.frequency & 0xFF) as u8,
415 ];
416 let bw_bytes = [
417 (sub.bandwidth >> 24) as u8,
418 (sub.bandwidth >> 16) as u8,
419 (sub.bandwidth >> 8) as u8,
420 (sub.bandwidth & 0xFF) as u8,
421 ];
422 let txp = if sub.txpower < 0 {
423 (sub.txpower as i16 + 256) as u8
424 } else {
425 sub.txpower as u8
426 };
427
428 if multi {
429 w.write_all(&rnode_kiss::rnode_select_command(index, rnode_kiss::CMD_FREQUENCY, &freq_bytes))?;
430 w.write_all(&rnode_kiss::rnode_select_command(index, rnode_kiss::CMD_BANDWIDTH, &bw_bytes))?;
431 w.write_all(&rnode_kiss::rnode_select_command(index, rnode_kiss::CMD_TXPOWER, &[txp]))?;
432 w.write_all(&rnode_kiss::rnode_select_command(index, rnode_kiss::CMD_SF, &[sub.spreading_factor]))?;
433 w.write_all(&rnode_kiss::rnode_select_command(index, rnode_kiss::CMD_CR, &[sub.coding_rate]))?;
434 } else {
435 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_FREQUENCY, &freq_bytes))?;
436 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_BANDWIDTH, &bw_bytes))?;
437 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_TXPOWER, &[txp]))?;
438 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_SF, &[sub.spreading_factor]))?;
439 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_CR, &[sub.coding_rate]))?;
440 }
441
442 if let Some(st) = sub.st_alock {
444 let st_val = (st * 100.0) as u16;
445 let st_bytes = [(st_val >> 8) as u8, (st_val & 0xFF) as u8];
446 if multi {
447 w.write_all(&rnode_kiss::rnode_select_command(index, rnode_kiss::CMD_ST_ALOCK, &st_bytes))?;
448 } else {
449 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_ST_ALOCK, &st_bytes))?;
450 }
451 }
452 if let Some(lt) = sub.lt_alock {
453 let lt_val = (lt * 100.0) as u16;
454 let lt_bytes = [(lt_val >> 8) as u8, (lt_val & 0xFF) as u8];
455 if multi {
456 w.write_all(&rnode_kiss::rnode_select_command(index, rnode_kiss::CMD_LT_ALOCK, <_bytes))?;
457 } else {
458 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_LT_ALOCK, <_bytes))?;
459 }
460 }
461
462 if multi {
464 w.write_all(&rnode_kiss::rnode_select_command(index, rnode_kiss::CMD_RADIO_STATE, &[rnode_kiss::RADIO_STATE_ON]))?;
465 } else {
466 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_RADIO_STATE, &[rnode_kiss::RADIO_STATE_ON]))?;
467 }
468
469 Ok(())
470}
471
472fn process_flow_queue(
474 flow_state: &Arc<Mutex<SubFlowState>>,
475 writer: &Arc<Mutex<std::fs::File>>,
476 index: u8,
477) {
478 let mut state = flow_state.lock().unwrap();
479 if let Some(data) = state.queue.pop_front() {
480 state.ready = false;
481 drop(state);
482 let frame = rnode_kiss::rnode_data_frame(index, &data);
483 let _ = writer.lock().unwrap().write_all(&frame);
484 } else {
485 state.ready = true;
486 }
487}
488
489use std::collections::HashMap;
492use rns_core::transport::types::InterfaceInfo;
493use super::{InterfaceFactory, InterfaceConfigData, StartContext, StartResult, SubInterface};
494
495pub struct RNodeFactory;
497
498impl InterfaceFactory for RNodeFactory {
499 fn type_name(&self) -> &str { "RNodeInterface" }
500
501 fn default_ifac_size(&self) -> usize { 8 }
502
503 fn parse_config(
504 &self,
505 name: &str,
506 id: InterfaceId,
507 params: &HashMap<String, String>,
508 ) -> Result<Box<dyn InterfaceConfigData>, String> {
509 let pre_opened_fd = params.get("fd")
510 .and_then(|v| v.parse::<i32>().ok());
511
512 let port = params.get("port")
513 .cloned()
514 .or_else(|| pre_opened_fd.map(|_| "usb-bridge".to_string()))
515 .ok_or_else(|| "RNodeInterface requires 'port' or 'fd'".to_string())?;
516
517 let speed = params.get("speed")
518 .and_then(|v| v.parse().ok())
519 .unwrap_or(115200u32);
520
521 let frequency = params.get("frequency")
522 .and_then(|v| v.parse().ok())
523 .unwrap_or(868_000_000u32);
524
525 let bandwidth = params.get("bandwidth")
526 .and_then(|v| v.parse().ok())
527 .unwrap_or(125_000u32);
528
529 let txpower = params.get("txpower")
530 .and_then(|v| v.parse().ok())
531 .unwrap_or(7i8);
532
533 let spreading_factor = params.get("spreadingfactor")
534 .or_else(|| params.get("spreading_factor"))
535 .and_then(|v| v.parse().ok())
536 .unwrap_or(8u8);
537
538 let coding_rate = params.get("codingrate")
539 .or_else(|| params.get("coding_rate"))
540 .and_then(|v| v.parse().ok())
541 .unwrap_or(5u8);
542
543 let flow_control = params.get("flow_control")
544 .and_then(|v| crate::config::parse_bool_pub(v))
545 .unwrap_or(false);
546
547 let st_alock = params.get("st_alock")
548 .and_then(|v| v.parse().ok());
549
550 let lt_alock = params.get("lt_alock")
551 .and_then(|v| v.parse().ok());
552
553 let id_interval = params.get("id_interval")
554 .and_then(|v| v.parse().ok());
555
556 let id_callsign = params.get("id_callsign")
557 .map(|v| v.as_bytes().to_vec());
558
559 let sub = RNodeSubConfig {
560 name: name.to_string(),
561 frequency,
562 bandwidth,
563 txpower,
564 spreading_factor,
565 coding_rate,
566 flow_control,
567 st_alock,
568 lt_alock,
569 };
570
571 Ok(Box::new(RNodeConfig {
572 name: name.to_string(),
573 port,
574 speed,
575 subinterfaces: vec![sub],
576 id_interval,
577 id_callsign,
578 base_interface_id: id,
579 pre_opened_fd,
580 }))
581 }
582
583 fn start(
584 &self,
585 config: Box<dyn InterfaceConfigData>,
586 ctx: StartContext,
587 ) -> std::io::Result<StartResult> {
588 let rnode_config = *config.into_any().downcast::<RNodeConfig>()
589 .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type"))?;
590
591 let name = rnode_config.name.clone();
592
593 let pairs = start(rnode_config, ctx.tx)?;
594
595 let mut subs = Vec::with_capacity(pairs.len());
596 for (index, (sub_id, writer)) in pairs.into_iter().enumerate() {
597 let sub_name = if index == 0 {
598 name.clone()
599 } else {
600 format!("{}/{}", name, index)
601 };
602
603 let info = InterfaceInfo {
604 id: sub_id,
605 name: sub_name,
606 mode: ctx.mode,
607 out_capable: true,
608 in_capable: true,
609 bitrate: None,
610 announce_rate_target: None,
611 announce_rate_grace: 0,
612 announce_rate_penalty: 0.0,
613 announce_cap: rns_core::constants::ANNOUNCE_CAP,
614 is_local_client: false,
615 wants_tunnel: false,
616 tunnel_id: None,
617 mtu: rns_core::constants::MTU as u32,
618 ingress_control: false,
619 ia_freq: 0.0,
620 started: crate::time::now(),
621 };
622
623 subs.push(SubInterface {
624 id: sub_id,
625 info,
626 writer,
627 interface_type_name: "RNodeInterface".to_string(),
628 });
629 }
630
631 Ok(StartResult::Multi(subs))
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use crate::kiss;
639 use crate::serial::open_pty_pair;
640 use std::os::unix::io::{AsRawFd, FromRawFd};
641 fn poll_read(fd: i32, timeout_ms: i32) -> bool {
643 let mut pfd = libc::pollfd {
644 fd,
645 events: libc::POLLIN,
646 revents: 0,
647 };
648 let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
649 ret > 0
650 }
651
652 fn read_available(file: &mut std::fs::File) -> Vec<u8> {
654 let mut all = Vec::new();
655 let mut buf = [0u8; 4096];
656 while poll_read(file.as_raw_fd(), 100) {
657 match file.read(&mut buf) {
658 Ok(n) if n > 0 => all.extend_from_slice(&buf[..n]),
659 _ => break,
660 }
661 }
662 all
663 }
664
665 fn mock_respond_detect(master: &mut std::fs::File) {
667 master
669 .write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_DETECT, &[rnode_kiss::DETECT_RESP]))
670 .unwrap();
671 master
673 .write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_FW_VERSION, &[0x01, 0x4A]))
674 .unwrap();
675 master
677 .write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_PLATFORM, &[0x80]))
678 .unwrap();
679 master
681 .write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_MCU, &[0x01]))
682 .unwrap();
683 master.flush().unwrap();
684 }
685
686 #[test]
687 fn rnode_detect_over_pty() {
688 let (master_fd, slave_fd) = open_pty_pair().unwrap();
690 let mut master = unsafe { std::fs::File::from_raw_fd(master_fd) };
691 let mut slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
692
693 mock_respond_detect(&mut master);
695
696 assert!(poll_read(slave.as_raw_fd(), 2000));
698 let mut decoder = rnode_kiss::RNodeDecoder::new();
699 let mut buf = [0u8; 4096];
700 let n = slave.read(&mut buf).unwrap();
701 let events = decoder.feed(&buf[..n]);
702
703 assert!(
704 events.iter().any(|e| matches!(e, rnode_kiss::RNodeEvent::Detected(true))),
705 "should detect device"
706 );
707 assert!(
708 events.iter().any(|e| matches!(e, rnode_kiss::RNodeEvent::FirmwareVersion { .. })),
709 "should get firmware version"
710 );
711 }
712
713 #[test]
714 fn rnode_configure_commands() {
715 let (master_fd, slave_fd) = open_pty_pair().unwrap();
716 let mut master = unsafe { std::fs::File::from_raw_fd(master_fd) };
717 let writer_file = unsafe { std::fs::File::from_raw_fd(libc::dup(slave_fd)) };
718 let writer = Arc::new(Mutex::new(writer_file));
719
720 let sub = RNodeSubConfig {
721 name: "test".into(),
722 frequency: 868_000_000,
723 bandwidth: 125_000,
724 txpower: 7,
725 spreading_factor: 8,
726 coding_rate: 5,
727 flow_control: false,
728 st_alock: None,
729 lt_alock: None,
730 };
731
732 configure_subinterface(&writer, 0, &sub, false).unwrap();
733
734 let data = read_available(&mut master);
736
737 assert!(data.windows(2).any(|w| w[0] == kiss::FEND && w[1] == rnode_kiss::CMD_FREQUENCY),
739 "should contain FREQUENCY command");
740 assert!(data.windows(2).any(|w| w[0] == kiss::FEND && w[1] == rnode_kiss::CMD_BANDWIDTH),
742 "should contain BANDWIDTH command");
743 assert!(data.windows(3).any(|w| w[0] == kiss::FEND && w[1] == rnode_kiss::CMD_RADIO_STATE && w[2] == rnode_kiss::RADIO_STATE_ON),
745 "should contain RADIO_STATE ON command");
746
747 unsafe { libc::close(slave_fd) };
748 }
749
750 #[test]
751 fn rnode_data_roundtrip() {
752 let (master_fd, slave_fd) = open_pty_pair().unwrap();
753 let mut master = unsafe { std::fs::File::from_raw_fd(master_fd) };
754 let slave = unsafe { std::fs::File::from_raw_fd(slave_fd) };
755
756 let payload = vec![0x01, 0x02, 0x03, 0x04, 0x05];
758 let frame = rnode_kiss::rnode_data_frame(0, &payload);
759 master.write_all(&frame).unwrap();
760 master.flush().unwrap();
761
762 assert!(poll_read(slave.as_raw_fd(), 2000));
764 let mut decoder = rnode_kiss::RNodeDecoder::new();
765 let mut buf = [0u8; 4096];
766 let mut slave_file = slave;
767 let n = slave_file.read(&mut buf).unwrap();
768 let events = decoder.feed(&buf[..n]);
769
770 assert_eq!(events.len(), 1);
771 match &events[0] {
772 rnode_kiss::RNodeEvent::DataFrame { index, data } => {
773 assert_eq!(*index, 0);
774 assert_eq!(data, &payload);
775 }
776 other => panic!("expected DataFrame, got {:?}", other),
777 }
778 }
779
780 #[test]
781 fn rnode_flow_control() {
782 let (master_fd, slave_fd) = open_pty_pair().unwrap();
783 let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
784 let shared_writer = Arc::new(Mutex::new(writer_file));
785
786 let flow_state = Arc::new(Mutex::new(SubFlowState {
787 ready: true,
788 queue: std::collections::VecDeque::new(),
789 }));
790
791 let mut sub_writer = RNodeSubWriter {
792 writer: shared_writer.clone(),
793 index: 0,
794 flow_control: true,
795 flow_state: flow_state.clone(),
796 };
797
798 sub_writer.send_frame(b"hello").unwrap();
800 assert!(!flow_state.lock().unwrap().ready);
801
802 sub_writer.send_frame(b"world").unwrap();
804 assert_eq!(flow_state.lock().unwrap().queue.len(), 1);
805
806 process_flow_queue(&flow_state, &shared_writer, 0);
808 assert_eq!(flow_state.lock().unwrap().queue.len(), 0);
809 assert!(!flow_state.lock().unwrap().ready); process_flow_queue(&flow_state, &shared_writer, 0);
813 assert!(flow_state.lock().unwrap().ready);
814
815 unsafe { libc::close(master_fd) };
816 }
817
818 #[test]
819 fn rnode_sub_writer_format() {
820 let (master_fd, slave_fd) = open_pty_pair().unwrap();
821 let mut master = unsafe { std::fs::File::from_raw_fd(master_fd) };
822 let writer_file = unsafe { std::fs::File::from_raw_fd(slave_fd) };
823 let shared_writer = Arc::new(Mutex::new(writer_file));
824
825 let flow_state = Arc::new(Mutex::new(SubFlowState {
826 ready: true,
827 queue: std::collections::VecDeque::new(),
828 }));
829
830 let mut sub_writer = RNodeSubWriter {
831 writer: shared_writer,
832 index: 1, flow_control: false,
834 flow_state,
835 };
836
837 let payload = vec![0xAA, 0xBB, 0xCC];
838 sub_writer.send_frame(&payload).unwrap();
839
840 assert!(poll_read(master.as_raw_fd(), 2000));
842 let mut buf = [0u8; 256];
843 let n = master.read(&mut buf).unwrap();
844
845 assert_eq!(buf[0], kiss::FEND);
847 assert_eq!(buf[1], 0x10); assert_eq!(buf[n - 1], kiss::FEND);
849 }
850
851 #[test]
852 fn rnode_multi_sub_routing() {
853 let mut decoder = rnode_kiss::RNodeDecoder::new();
855
856 let payload0 = vec![0x01, 0x02];
857 let frame0 = rnode_kiss::rnode_data_frame(0, &payload0);
858 let events0 = decoder.feed(&frame0);
859 assert_eq!(events0.len(), 1);
860 assert_eq!(events0[0], rnode_kiss::RNodeEvent::DataFrame { index: 0, data: payload0 });
861
862 let payload1 = vec![0x03, 0x04];
863 let frame1 = rnode_kiss::rnode_data_frame(1, &payload1);
864 let events1 = decoder.feed(&frame1);
865 assert_eq!(events1.len(), 1);
866 assert_eq!(events1[0], rnode_kiss::RNodeEvent::DataFrame { index: 1, data: payload1 });
867 }
868
869 #[test]
870 fn rnode_error_handling() {
871 let mut decoder = rnode_kiss::RNodeDecoder::new();
879 let frame = rnode_kiss::rnode_command(rnode_kiss::CMD_ERROR, &[0x02]);
880 let events = decoder.feed(&frame);
881 assert_eq!(events.len(), 1);
882 assert_eq!(
884 events[0],
885 rnode_kiss::RNodeEvent::DataFrame {
886 index: 5,
887 data: vec![0x02]
888 }
889 );
890 }
891
892 #[test]
893 fn rnode_config_validation() {
894 let good = RNodeSubConfig {
895 name: "test".into(),
896 frequency: 868_000_000,
897 bandwidth: 125_000,
898 txpower: 7,
899 spreading_factor: 8,
900 coding_rate: 5,
901 flow_control: false,
902 st_alock: None,
903 lt_alock: None,
904 };
905 assert!(validate_sub_config(&good).is_none());
906
907 let mut bad = good.clone();
909 bad.frequency = 100;
910 assert!(validate_sub_config(&bad).is_some());
911
912 bad = good.clone();
914 bad.spreading_factor = 13;
915 assert!(validate_sub_config(&bad).is_some());
916
917 bad = good.clone();
919 bad.coding_rate = 9;
920 assert!(validate_sub_config(&bad).is_some());
921
922 bad = good.clone();
924 bad.bandwidth = 5;
925 assert!(validate_sub_config(&bad).is_some());
926
927 bad = good.clone();
929 bad.txpower = 50;
930 assert!(validate_sub_config(&bad).is_some());
931 }
932}