Something went wrong. Try again.
An interface for Uniden radio scanners, written in Rust
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324use tokio_util::{ bytes::{BufMut, Bytes, BytesMut}, codec::{AnyDelimiterCodec, AnyDelimiterCodecError, Decoder, Encoder},};
use crate::{ bytes_split::BytesSplit, command::{Command, ParamBuffer, Params, Response},};
pub(crate) const RETURN_CODE: u8 = b'\r';pub(crate) const PARAM_DELIMITER: u8 = b',';
#[derive(Clone, Debug)]pub struct Codec { decoder: AnyDelimiterCodec,}
impl Codec { pub fn new() -> Self { Self { decoder: AnyDelimiterCodec::new(vec![RETURN_CODE], vec![RETURN_CODE]), } }}
impl<Cmd> Encoder<Cmd> for Codecwhere Cmd: Command,{ type Error = std::io::Error;
fn encode(&mut self, item: Cmd, dst: &mut BytesMut) -> Result<(), Self::Error> { let params = item.params(); let est_len = Cmd::TEXT.len() + params.count() + params.max_size() + 1; dst.reserve(est_len);
dst.extend_from_slice(Cmd::TEXT); params.serialize_to(ParamBuffer::new(dst));
dst.put_u8(RETURN_CODE);
Ok(()) }}
#[derive(Debug, thiserror::Error)]pub enum ResponseError<E> { #[error("response is for wrong command")] WrongCommand,
#[error("unexpected number of fields")] WrongNumberOfFields,
#[error(transparent)] InvalidFields(#[from] E),}
#[derive(Clone, Debug)]pub struct RawResponse { cmd: Bytes, raw_values: Vec<Bytes>,}
impl RawResponse { pub fn deserialize<Cmd: Command>( &self, ) -> Result<Cmd::Response, ResponseError<<Cmd::Response as Response>::Error>> { if self.cmd != Cmd::TEXT { return Err(ResponseError::WrongCommand); } if self.raw_values.len() != Cmd::Response::expected_field_count() { return Err(ResponseError::WrongNumberOfFields); }
let response = Cmd::Response::deserialize(&self.raw_values)?;
Ok(response) }}
#[derive(Debug, thiserror::Error)]pub enum DecoderError { #[error("malformed response")] Malformed,
#[error("command must be executed in program mode")] NotAcceptable,
#[error("command format error or invalid value")] ErrorResponse,
#[error(transparent)] DelimiterError(#[from] AnyDelimiterCodecError),
#[error(transparent)] Io(#[from] std::io::Error),}
impl Decoder for Codec { type Item = RawResponse; type Error = DecoderError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { // internal `AnyDelimiterCodec` is responsible for managing the buffer per the semantics of // `Decoder` let Some(output) = self.decoder.decode(src)? else { return Ok(None); };
let mut all_fields = BytesSplit::new(output, PARAM_DELIMITER);
let Some(cmd) = all_fields.next() else { return Err(DecoderError::Malformed); };
let raw_values = all_fields.collect::<Vec<_>>();
if raw_values.len() == 1 { match raw_values[0].as_ref() { b"NG" => return Err(DecoderError::NotAcceptable), b"ERR" => return Err(DecoderError::ErrorResponse), _ => {} } }
Ok(Some(RawResponse { cmd, raw_values })) }}
#[cfg(test)]mod tests { use claims::{assert_matches, assert_none, assert_ok}; use tokio_util::{ bytes::{Bytes, BytesMut}, codec::{AnyDelimiterCodec, AnyDelimiterCodecError, Decoder, Encoder}, };
use crate::{ Command, DecoderError, OkResponseError, ResponseError, codec::{Codec, RETURN_CODE, RawResponse}, command::{OkResponse, command, range_param, range_response}, };
range_param!(RangeParam(0..=15): u8); range_response!(RangeParam => ParamError : Invalid("invalid"));
command!(b"CMD": SimpleCommand); command!(b"CMD": ResponseCommand => RangeParam); command!(b"CMD": ParamCommand(RangeParam) => OkResponse);
mod encode { use super::*;
#[test] fn simple_command() { let buf = encode(SimpleCommand);
assert_eq!(buf.as_ref(), b"CMD\r"); }
#[test] fn single_param() { let buf = encode(ParamCommand(RangeParam::new(0).unwrap()));
assert_eq!(buf.as_ref(), b"CMD,0\r"); }
fn encode<C: Command>(cmd: C) -> BytesMut { let mut buf = BytesMut::new(); Codec::new().encode(cmd, &mut buf).unwrap(); buf } }
mod decode { use super::*;
#[test] fn ok_response() { let item = decode(b"CMD,OK\r").unwrap().unwrap();
assert_eq!(item.cmd.as_ref(), b"CMD"); assert_eq!(item.raw_values, [Bytes::from(b"OK".as_slice())]); }
#[test] fn multi_param_response() { let item = decode(b"CMD,1,2,3\r").unwrap().unwrap();
assert_eq!(item.cmd.as_ref(), b"CMD"); assert_eq!( item.raw_values, [ Bytes::from(b"1".as_slice()), Bytes::from(b"2".as_slice()), Bytes::from(b"3".as_slice()) ], ); }
#[test] fn incomplete_response_returns_none() { let (response, result) = decode_buf(b"CMD,");
assert_none!(assert_ok!(result)); // buffer should not be changed assert_eq!(response.as_ref(), b"CMD,"); }
#[test] fn clears_buffer_on_complete_response() { let (response, result) = decode_buf(b"CMD,OK\r");
assert_eq!(result.unwrap().unwrap().cmd.as_ref(), b"CMD"); // buffer should be cleared assert!(response.is_empty()); }
#[test] fn partially_consumes_buffer_on_complete_response() { let (response, result) = decode_buf(b"CMD,OK\rCMD2");
assert_eq!(result.unwrap().unwrap().cmd.as_ref(), b"CMD"); // buffer should have single complete command removed assert_eq!(response.as_ref(), b"CMD2"); }
#[test] fn malformed_response() { let err = decode(b"\r").unwrap_err();
assert_matches!(err, DecoderError::Malformed); }
#[test] fn not_acceptable_response() { let err = decode(b"CMD,NG\r").unwrap_err();
assert_matches!(err, DecoderError::NotAcceptable); }
#[test] fn error_response() { let err = decode(b"CMD,ERR\r").unwrap_err();
assert_matches!(err, DecoderError::ErrorResponse); }
#[test] fn delimiter_error() { let mut codec = Codec::new(); // reduce the max length of the internal delimiter decoder codec.decoder = AnyDelimiterCodec::new_with_max_length(vec![RETURN_CODE], vec![RETURN_CODE], 1); let mut response = BytesMut::from(b"CMD,OK\r".as_slice());
let err = codec.decode(&mut response).unwrap_err(); assert_matches!( err, DecoderError::DelimiterError(AnyDelimiterCodecError::MaxChunkLengthExceeded) ); }
fn decode(raw_response: &[u8]) -> Result<Option<RawResponse>, DecoderError> { decode_buf(raw_response).1 }
fn decode_buf( raw_response: &[u8], ) -> (BytesMut, Result<Option<RawResponse>, DecoderError>) { let mut raw_response = BytesMut::from(raw_response); let result = Codec::new().decode(&mut raw_response);
(raw_response, result) } }
mod deserialize { use super::*;
#[test] fn ok_response() { decode(b"CMD,OK\r").deserialize::<SimpleCommand>().unwrap(); // nothing to assert }
#[test] fn single_param_response() { let response = decode(b"CMD,1\r").deserialize::<ResponseCommand>().unwrap(); assert_eq!(response.value(), 1); }
#[test] fn wrong_command() { let result = decode(b"FOO,OK\r") .deserialize::<SimpleCommand>() .unwrap_err(); assert_matches!(result, ResponseError::WrongCommand); }
#[test] fn wrong_number_of_fields() { let result = decode(b"CMD,OK,OK\r") .deserialize::<SimpleCommand>() .unwrap_err(); assert_matches!(result, ResponseError::WrongNumberOfFields); }
#[test] fn invalid_fields() { let result = decode(b"CMD,FOO\r") .deserialize::<SimpleCommand>() .unwrap_err(); assert_matches!(result, ResponseError::InvalidFields(OkResponseError)); }
fn decode(raw: &[u8]) -> RawResponse { let mut raw = BytesMut::from(raw); Codec::new().decode(&mut raw).unwrap().unwrap() } }}