kiss-tnc 0.1.2

A library to connect to KISS TNCs, such as Direwolf
Documentation
use crate::command::Command;
use crate::errors::*;
use std::io::{Read, Write};
use std::net::{TcpStream, ToSocketAddrs};

pub struct Tnc<T: Read + Write> {
	pub stream: T,
	port: u8,
}

impl Tnc<TcpStream> {
	/// Connects to a KISS TNC over TCP.
	/// # Arguments
	///
	/// * addr - The address to connect to.
	pub fn connect<A: ToSocketAddrs>(addr: A) -> std::io::Result<Self> {
		let stream = TcpStream::connect(addr)?;

		Ok(Self { stream, port: 0 })
	}
}

impl<T: Read + Write> Tnc<T> {
	/// Construct a Tnc from anything that implements Read + Write
	pub fn new(stream: T) -> Self {
		Self { stream, port: 0 }
	}

	/// Sets the port to use for transmissions.
	/// # Arguments
	///
	/// * port - The port number. Valid values are 0 to 15.
	pub fn set_port(&mut self, port: u8) {
		if port > 15 {
			panic!("Port number is invalid");
		}

		self.port = port;
	}

	/// Send an array of bytes as a frame. Note that the bytes are not split up if there are a lot of them.
	pub fn send_frame(&mut self, data: &[u8]) -> std::io::Result<usize> {
		let command = Command::borrowed_frame(data);
		command.write(self.port, &mut self.stream)
	}

	/// Sets the amount of time between keying the transmitter and beginning to send data
	/// # Arguments
	///
	/// * delay - The amount of time to wait, in 10 ms units
	pub fn set_tx_delay(&mut self, delay: u8) -> std::io::Result<usize> {
		let command = Command::TxDelay(delay);
		command.write(self.port, &mut self.stream)
	}

	/// Exits KISS mode
	pub fn exit(mut self) -> std::io::Result<usize> {
		let command = Command::Return;
		let c = command.write(self.port, &mut self.stream)?;
		self.flush()?;
		Ok(c)
	}

	/// Flushes the transmission buffer.
	pub fn flush(&mut self) -> std::io::Result<()> {
		self.stream.flush()
	}

	/// Reads a frame from the TNC. Returns a tuple containing the port number
	/// as well as the data contained in the frame.
	pub fn read_frame(&mut self) -> Result<(u8, Vec<u8>), ReadError> {
		let (port, cmd) = Command::read(&mut self.stream)?;
		if let Command::DataFrame(data) = cmd {
			Ok((port, data.into_owned()))
		} else {
			Err(ReadError::MalformedData)
		}
	}
}

mod tests {
	use super::*;

	#[derive(Default)]
	struct MockStream {
		read_buffer: Vec<u8>,
		write_buffer: Vec<u8>,
	}

	impl Write for MockStream {
		fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
			self.write_buffer.extend(bytes);
			Ok(bytes.len())
		}

		fn flush(&mut self) -> Result<(), std::io::Error> {
			Ok(())
		}
	}

	impl Read for MockStream {
		fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
			let len = buf.len().min(self.read_buffer.len());
			buf[..len].clone_from_slice(&self.read_buffer[..len]);
			self.read_buffer = self.read_buffer[len..].to_vec();

			Ok(len)
		}
	}

	#[test]
	fn test_send_frame_works_corrently() {
		let mut tnc = Tnc {
			port: 0,
			stream: MockStream::default(),
		};

		assert_eq!(7, tnc.send_frame("TEST".as_bytes()).unwrap());
		assert_eq!(
			vec![0xC0, 0x00, 0x54, 0x45, 0x53, 0x54, 0xC0],
			tnc.stream.write_buffer
		);

		let mut tnc = Tnc {
			port: 0,
			stream: MockStream::default(),
		};

		tnc.set_port(5);
		assert_eq!(8, tnc.send_frame("Hello".as_bytes()).unwrap());
		assert_eq!(
			vec![0xC0, 0x50, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xC0],
			tnc.stream.write_buffer
		);

		let mut tnc = Tnc {
			port: 0,
			stream: MockStream::default(),
		};

		assert_eq!(7, tnc.send_frame(&[0xC0, 0xDB]).unwrap());
		assert_eq!(
			vec![0xC0, 0x00, 0xDB, 0xDC, 0xDB, 0xDD, 0xC0],
			tnc.stream.write_buffer
		);
	}

	#[test]
	fn test_set_tx_delay_works_correctly() {
		let mut tnc = Tnc {
			port: 14,
			stream: MockStream::default(),
		};

		// Delay of 100 * 10 ms = 1 second
		assert_eq!(4, tnc.set_tx_delay(100).unwrap());
		assert_eq!(vec![0xC0, 0xE1, 100, 0xC0], tnc.stream.write_buffer);
	}

	#[test]
	fn test_read_frame_works_correctly() {
		let mut tnc = Tnc {
			port: 0,
			stream: MockStream {
				read_buffer: vec![0xC0, 0x00, 0x54, 0x45, 0x53, 0x54, 0xC0],
				write_buffer: vec![],
			},
		};

		let (port, data) = tnc.read_frame().unwrap();
		assert_eq!(0, port);
		assert_eq!("TEST".as_bytes(), data);
	}

	#[test]
	fn test_read_frame_sets_port_correctly() {
		let mut tnc = Tnc {
			port: 0,
			stream: MockStream {
				read_buffer: vec![0xC0, 0x50, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xC0],
				write_buffer: vec![],
			},
		};

		let (port, data) = tnc.read_frame().unwrap();
		assert_eq!(5, port);
		assert_eq!("Hello".as_bytes(), data);
	}

	#[test]
	fn test_read_frame_multiple_frames() {
		let mut tnc = Tnc {
			port: 0,
			stream: MockStream {
				read_buffer: vec![
					0xC0, 0x50, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xC0, 0xC0, 0x00, 0x54, 0x45, 0x53,
					0x54, 0xC0,
				],
				write_buffer: vec![],
			},
		};

		let (port, data) = tnc.read_frame().unwrap();
		assert_eq!(5, port);
		assert_eq!("Hello".as_bytes(), data);

		let (port, data) = tnc.read_frame().unwrap();
		assert_eq!(0, port);
		assert_eq!("TEST".as_bytes(), data);
	}

	#[test]
	fn test_read_frame_escape_works_correctly() {
		let mut tnc = Tnc {
			port: 0,
			stream: MockStream {
				read_buffer: vec![0xC0, 0x00, 0xDB, 0xDC, 0xDB, 0xDD, 0xC0],
				write_buffer: vec![],
			},
		};

		let (port, data) = tnc.read_frame().unwrap();
		assert_eq!(0, port);
		assert_eq!(vec![0xC0, 0xDB], data);
	}

	#[test]
	fn test_read_frame_ignores_additional_c0s() {
		let mut tnc = Tnc {
			port: 0,
			stream: MockStream {
				read_buffer: vec![
					0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0xDB, 0xDC, 0xDB, 0xDD, 0xC0,
				],
				write_buffer: vec![],
			},
		};

		let (port, data) = tnc.read_frame().unwrap();
		assert_eq!(0, port);
		assert_eq!(vec![0xC0, 0xDB], data);
	}

	#[test]
	fn test_read_frame_requires_c0() {
		let mut tnc = Tnc {
			port: 0,
			stream: MockStream {
				read_buffer: vec![0x00, 0xDB, 0xDC, 0xDB, 0xDD, 0xC0],
				write_buffer: vec![],
			},
		};

		assert!(matches!(
			tnc.read_frame().unwrap_err(),
			ReadError::MalformedData
		));
	}

	#[test]
	fn test_bytes_after_c0_are_ignored() {
		let mut tnc = Tnc {
			port: 0,
			stream: MockStream {
				read_buffer: vec![
					0xC0, 0x00, 0xDB, 0xDC, 0xDB, 0xDD, 0xC0, 0xA, 0xB, 0xC, 0xAB, 0xC0, 0x12, 0x53,
				],
				write_buffer: vec![],
			},
		};

		let (port, data) = tnc.read_frame().unwrap();
		assert_eq!(0, port);
		assert_eq!(vec![0xC0, 0xDB], data);
	}
}