1use std::time::Duration;
34
35use kiss_tnc::{
36 CMD_DATA, CMD_FULL_DUPLEX, CMD_PERSISTENCE, CMD_RETURN, CMD_SET_HARDWARE, CMD_SLOT_TIME,
37 CMD_TX_DELAY, CMD_TX_TAIL, FEND, KissFrame, decode_kiss_frame, encode_kiss_frame,
38};
39
40use crate::error::{Error, ProtocolError, TransportError};
41use crate::protocol::{Codec, Command, Response};
42use crate::transport::Transport;
43use crate::types::{TncBaud, TncMode};
44
45use super::Radio;
46
47const KISS_RECEIVE_TIMEOUT: Duration = Duration::from_secs(10);
49
50pub struct KissSession<T: Transport> {
69 pub(crate) transport: T,
71 codec: Codec,
73 notifications: tokio::sync::broadcast::Sender<Response>,
75 timeout: Duration,
77 mode_a: Option<super::RadioMode>,
79 mode_b: Option<super::RadioMode>,
81 mcp_speed: super::programming::McpSpeed,
83 receive_timeout: Duration,
85 read_buf: Vec<u8>,
87}
88
89impl<T: Transport> std::fmt::Debug for KissSession<T> {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 f.debug_struct("KissSession")
92 .field("receive_timeout", &self.receive_timeout)
93 .field("read_buf_len", &self.read_buf.len())
94 .finish_non_exhaustive()
95 }
96}
97
98impl<T: Transport> Radio<T> {
99 pub async fn enter_kiss(mut self, baud: TncBaud) -> Result<KissSession<T>, (Self, Error)> {
110 tracing::info!(?baud, "entering KISS mode");
111 let response = match self
112 .execute(Command::SetTncMode {
113 mode: TncMode::Kiss,
114 setting: baud,
115 })
116 .await
117 {
118 Ok(r) => r,
119 Err(e) => return Err((self, e)),
120 };
121 match response {
122 Response::TncMode { .. } => {}
123 other => {
124 return Err((
125 self,
126 Error::Protocol(ProtocolError::UnexpectedResponse {
127 expected: "TncMode".into(),
128 actual: format!("{other:?}").into_bytes(),
129 }),
130 ));
131 }
132 }
133
134 Ok(KissSession {
135 transport: self.transport,
136 codec: self.codec,
137 notifications: self.notifications,
138 timeout: self.timeout,
139 mode_a: self.mode_a,
140 mode_b: self.mode_b,
141 mcp_speed: self.mcp_speed,
142 receive_timeout: KISS_RECEIVE_TIMEOUT,
143 read_buf: Vec::with_capacity(512),
144 })
145 }
146}
147
148impl<T: Transport> KissSession<T> {
149 pub const fn set_receive_timeout(&mut self, duration: Duration) {
153 self.receive_timeout = duration;
154 }
155
156 pub async fn send_wire(&mut self, wire: &[u8]) -> Result<(), Error> {
169 tracing::debug!(wire_len = wire.len(), "KISS TX (raw wire)");
170 self.transport.write(wire).await.map_err(Error::Transport)
171 }
172
173 pub async fn send_frame(&mut self, frame: &KissFrame) -> Result<(), Error> {
182 let wire = encode_kiss_frame(frame);
183 tracing::debug!(
184 command = frame.command,
185 data_len = frame.data.len(),
186 wire_len = wire.len(),
187 "KISS TX"
188 );
189 self.transport.write(&wire).await.map_err(Error::Transport)
190 }
191
192 pub async fn receive_frame(&mut self) -> Result<KissFrame, Error> {
204 let timeout_dur = self.receive_timeout;
205 tokio::time::timeout(timeout_dur, self.receive_frame_inner())
206 .await
207 .map_err(|_| Error::Timeout(timeout_dur))?
208 }
209
210 async fn receive_frame_inner(&mut self) -> Result<KissFrame, Error> {
212 let mut tmp = [0u8; 1024];
213 loop {
214 if let Some(frame) = Self::try_extract_frame(&mut self.read_buf) {
216 return Ok(frame);
217 }
218
219 let n = self
221 .transport
222 .read(&mut tmp)
223 .await
224 .map_err(Error::Transport)?;
225 if n == 0 {
226 return Err(Error::Transport(TransportError::Disconnected(
227 std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "connection closed"),
228 )));
229 }
230 self.read_buf.extend_from_slice(&tmp[..n]);
231 }
232 }
233
234 fn try_extract_frame(buf: &mut Vec<u8>) -> Option<KissFrame> {
240 while buf.first() == Some(&FEND) && buf.len() > 1 && buf[1] == FEND {
242 let _ = buf.remove(0);
243 }
244
245 if buf.len() < 3 || buf[0] != FEND {
247 return None;
248 }
249
250 let end_pos = buf[1..].iter().position(|&b| b == FEND)?;
252 let frame_end = end_pos + 2; let frame_bytes: Vec<u8> = buf.drain(..frame_end).collect();
255 match decode_kiss_frame(&frame_bytes) {
256 Ok(frame) => {
257 tracing::debug!(
258 command = frame.command,
259 data_len = frame.data.len(),
260 "KISS RX"
261 );
262 Some(frame)
263 }
264 Err(e) => {
265 tracing::warn!(?e, "discarding malformed KISS frame");
266 None
267 }
268 }
269 }
270
271 pub async fn set_tx_delay(&mut self, tens_of_ms: u8) -> Result<(), Error> {
280 tracing::debug!(tens_of_ms, "setting KISS TX delay");
281 self.send_frame(&KissFrame {
282 port: 0,
283 command: CMD_TX_DELAY,
284 data: vec![tens_of_ms],
285 })
286 .await
287 }
288
289 pub async fn set_persistence(&mut self, value: u8) -> Result<(), Error> {
298 tracing::debug!(value, "setting KISS persistence");
299 self.send_frame(&KissFrame {
300 port: 0,
301 command: CMD_PERSISTENCE,
302 data: vec![value],
303 })
304 .await
305 }
306
307 pub async fn set_slot_time(&mut self, tens_of_ms: u8) -> Result<(), Error> {
315 tracing::debug!(tens_of_ms, "setting KISS slot time");
316 self.send_frame(&KissFrame {
317 port: 0,
318 command: CMD_SLOT_TIME,
319 data: vec![tens_of_ms],
320 })
321 .await
322 }
323
324 pub async fn set_tx_tail(&mut self, tens_of_ms: u8) -> Result<(), Error> {
332 tracing::debug!(tens_of_ms, "setting KISS TX tail");
333 self.send_frame(&KissFrame {
334 port: 0,
335 command: CMD_TX_TAIL,
336 data: vec![tens_of_ms],
337 })
338 .await
339 }
340
341 pub async fn set_full_duplex(&mut self, full_duplex: bool) -> Result<(), Error> {
349 tracing::debug!(full_duplex, "setting KISS duplex mode");
350 self.send_frame(&KissFrame {
351 port: 0,
352 command: CMD_FULL_DUPLEX,
353 data: vec![u8::from(full_duplex)],
354 })
355 .await
356 }
357
358 pub async fn set_hardware_baud(&mut self, baud_1200: bool) -> Result<(), Error> {
368 let value = if baud_1200 { 0x00 } else { 0x05 };
369 tracing::debug!(baud_1200, value, "setting KISS hardware baud");
370 self.send_frame(&KissFrame {
371 port: 0,
372 command: CMD_SET_HARDWARE,
373 data: vec![value],
374 })
375 .await
376 }
377
378 pub async fn send_data(&mut self, ax25_bytes: &[u8]) -> Result<(), Error> {
387 self.send_frame(&KissFrame {
388 port: 0,
389 command: CMD_DATA,
390 data: ax25_bytes.to_vec(),
391 })
392 .await
393 }
394
395 pub async fn exit(mut self) -> Result<Radio<T>, Error> {
403 tracing::info!("exiting KISS mode");
404 self.send_frame(&KissFrame {
405 port: 0,
406 command: CMD_RETURN,
407 data: vec![],
408 })
409 .await?;
410
411 tokio::time::sleep(Duration::from_millis(100)).await;
413
414 Ok(Radio {
416 transport: self.transport,
417 codec: self.codec,
418 notifications: self.notifications,
419 timeout: self.timeout,
420 mode_a: self.mode_a,
421 mode_b: self.mode_b,
422 mcp_speed: self.mcp_speed,
423 last_cmd_time: None,
424 })
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 use crate::transport::MockTransport;
432 use crate::types::TncBaud;
433 use kiss_tnc::{CMD_DATA, FEND};
434
435 async fn mock_radio_for_kiss(baud: TncBaud) -> Radio<MockTransport> {
437 let tn_cmd = format!("TN 2,{}\r", u8::from(baud));
438 let tn_resp = format!("TN 2,{}\r", u8::from(baud));
439 let mut mock = MockTransport::new();
440 mock.expect(tn_cmd.as_bytes(), tn_resp.as_bytes());
441 Radio::connect(mock).await.unwrap()
442 }
443
444 #[tokio::test]
445 async fn enter_kiss_sends_tn_command() {
446 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
447 let session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
448 assert!(format!("{session:?}").contains("KissSession"));
450 }
451
452 #[tokio::test]
453 async fn enter_kiss_9600_baud() {
454 let radio = mock_radio_for_kiss(TncBaud::Bps9600).await;
455 let _session = radio.enter_kiss(TncBaud::Bps9600).await.unwrap();
456 }
457
458 #[tokio::test]
459 async fn send_frame_writes_kiss_encoded() {
460 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
461 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
462
463 session
466 .transport
467 .expect(&[FEND, 0x00, 0xAA, 0xBB, FEND], &[]);
468
469 let frame = KissFrame {
470 port: 0,
471 command: CMD_DATA,
472 data: vec![0xAA, 0xBB],
473 };
474 session.send_frame(&frame).await.unwrap();
475 }
476
477 #[tokio::test]
478 async fn send_data_wraps_in_kiss() {
479 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
480 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
481
482 session
483 .transport
484 .expect(&[FEND, 0x00, 0x01, 0x02, FEND], &[]);
485
486 session.send_data(&[0x01, 0x02]).await.unwrap();
487 }
488
489 #[tokio::test]
490 async fn set_tx_delay_sends_correct_frame() {
491 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
492 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
493
494 session.transport.expect(&[FEND, 0x01, 50, FEND], &[]);
496
497 session.set_tx_delay(50).await.unwrap();
498 }
499
500 #[tokio::test]
501 async fn set_persistence_sends_correct_frame() {
502 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
503 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
504
505 session.transport.expect(&[FEND, 0x02, 128, FEND], &[]);
506
507 session.set_persistence(128).await.unwrap();
508 }
509
510 #[tokio::test]
511 async fn set_slot_time_sends_correct_frame() {
512 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
513 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
514
515 session.transport.expect(&[FEND, 0x03, 10, FEND], &[]);
516
517 session.set_slot_time(10).await.unwrap();
518 }
519
520 #[tokio::test]
521 async fn set_tx_tail_sends_correct_frame() {
522 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
523 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
524
525 session.transport.expect(&[FEND, 0x04, 3, FEND], &[]);
526
527 session.set_tx_tail(3).await.unwrap();
528 }
529
530 #[tokio::test]
531 async fn set_full_duplex_sends_correct_frame() {
532 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
533 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
534
535 session.transport.expect(&[FEND, 0x05, 1, FEND], &[]);
536
537 session.set_full_duplex(true).await.unwrap();
538 }
539
540 #[tokio::test]
541 async fn set_hardware_baud_1200() {
542 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
543 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
544
545 session.transport.expect(&[FEND, 0x06, 0x00, FEND], &[]);
546
547 session.set_hardware_baud(true).await.unwrap();
548 }
549
550 #[tokio::test]
551 async fn set_hardware_baud_9600() {
552 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
553 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
554
555 session.transport.expect(&[FEND, 0x06, 0x05, FEND], &[]);
556
557 session.set_hardware_baud(false).await.unwrap();
558 }
559
560 #[tokio::test]
561 async fn exit_sends_return_and_restores_radio() {
562 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
563 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
564
565 session.transport.expect(&[FEND, 0xFF, FEND], &[]);
567
568 let _radio = session.exit().await.unwrap();
569 }
570
571 #[tokio::test]
572 async fn try_extract_frame_complete() {
573 let mut buf = vec![FEND, 0x00, 0xAA, FEND];
574 let frame = KissSession::<MockTransport>::try_extract_frame(&mut buf);
575 assert!(frame.is_some());
576 let frame = frame.unwrap();
577 assert_eq!(frame.command, CMD_DATA);
578 assert_eq!(frame.data, vec![0xAA]);
579 assert!(buf.is_empty());
580 }
581
582 #[tokio::test]
583 async fn try_extract_frame_incomplete() {
584 let mut buf = vec![FEND, 0x00, 0xAA];
585 let frame = KissSession::<MockTransport>::try_extract_frame(&mut buf);
586 assert!(frame.is_none());
587 assert_eq!(buf.len(), 3);
589 }
590
591 #[tokio::test]
592 async fn try_extract_frame_leading_fends() {
593 let mut buf = vec![FEND, FEND, FEND, 0x00, 0xBB, FEND];
594 let frame = KissSession::<MockTransport>::try_extract_frame(&mut buf);
595 assert!(frame.is_some());
596 let frame = frame.unwrap();
597 assert_eq!(frame.command, CMD_DATA);
598 assert_eq!(frame.data, vec![0xBB]);
599 }
600
601 #[tokio::test]
602 async fn set_receive_timeout() {
603 let radio = mock_radio_for_kiss(TncBaud::Bps1200).await;
604 let mut session = radio.enter_kiss(TncBaud::Bps1200).await.unwrap();
605 session.set_receive_timeout(Duration::from_secs(30));
606 assert_eq!(session.receive_timeout, Duration::from_secs(30));
607 }
608}