From 4ed832dc6cdcafeaf05aa165191dd28c3c23c6a0 Mon Sep 17 00:00:00 2001 From: Alex van de Sandt Date: Wed, 29 Apr 2026 15:24:50 -0500 Subject: [PATCH] Only deserialize values if the correct number are present --- src/codec.rs | 83 +++++++++++++++++++++++++++++++++----- src/command/bc125at.rs | 21 +++------- src/command/mod.rs | 8 ++-- src/command/ok_response.rs | 26 +++++------- 4 files changed, 92 insertions(+), 46 deletions(-) diff --git a/src/codec.rs b/src/codec.rs index f37fcbd..27c33e1 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -1,4 +1,4 @@ -use bytes::BufMut; +use bytes::{BufMut, Bytes}; use tokio_util::codec::{AnyDelimiterCodec, AnyDelimiterCodecError, Decoder, Encoder}; use crate::command::{Command, ParamSet, Response}; @@ -49,13 +49,16 @@ pub enum ResponseError { #[error("response is for wrong command")] WrongCommand, + #[error("unexpected number of fields")] + WrongNumberOfFields, + #[error(transparent)] InvalidFields(#[from] E), } pub struct RawResponse { - cmd: Vec, - raw_values: Vec, + cmd: Bytes, + raw_values: Vec, } impl RawResponse { @@ -68,8 +71,11 @@ impl RawResponse { 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::parse_from_values(self.raw_values.split(|b| *b == b','))?; + let response = Cmd::Response::parse_from_values(&self.raw_values)?; Ok(response) } @@ -99,14 +105,71 @@ impl Decoder for Codec { return Ok(None); }; - let mut fields = output.split(|b| *b == b','); - let Some(cmd) = fields.next() else { + let mut all_fields = BytesSplit::new(output, b','); + + let Some(cmd) = all_fields.next() else { return Err(DecoderError::Malformed); }; - Ok(Some(RawResponse { - cmd: cmd.to_owned(), - raw_values: output[4..].to_owned(), - })) + let raw_values = all_fields.collect::>(); + + Ok(Some(RawResponse { cmd, raw_values })) + } +} + +struct BytesSplit(Bytes, u8); + +impl BytesSplit { + fn new(inner: Bytes, split_at: u8) -> Self { + Self(inner, split_at) + } +} + +impl Iterator for BytesSplit { + type Item = Bytes; + + fn next(&mut self) -> Option { + if self.0.is_empty() { + return None; + } + + // find the index of the first delimiter + let Some(i) = self + .0 + .iter() + .enumerate() + .find_map(|(i, b)| (*b == self.1).then_some(i)) + else { + // we're on the last element + let last_elem = self.0.clone(); + self.0.clear(); + return Some(last_elem); + }; + + // extract the element + let elem = self.0.split_to(i); + // remove the comma + self.0 = self.0.slice(1..); + + Some(elem) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + + use super::*; + use bytes::Bytes; + + #[test] + fn split_works() { + let bytes = Bytes::from(b"foo,bar,baz".as_slice()); + let mut split = BytesSplit::new(bytes, b','); + + assert_eq!(split.next().unwrap().as_ref(), b"foo"); + assert_eq!(split.next().unwrap().as_ref(), b"bar"); + assert_eq!(split.next().unwrap().as_ref(), b"baz"); + assert_eq!(split.next(), None); } } diff --git a/src/command/bc125at.rs b/src/command/bc125at.rs index c3a9915..1bf2f78 100644 --- a/src/command/bc125at.rs +++ b/src/command/bc125at.rs @@ -19,8 +19,6 @@ impl Command<'static> for EnterProgramMode { pub enum FirmwareVersionError { #[error("invalid UTF-8 bytes")] Utf8Error(#[from] Utf8Error), - #[error("expected one response field")] - WrongNumberOfFields, } #[derive(Debug)] @@ -28,20 +26,13 @@ pub struct FirmwareVersion(pub String); impl Response for FirmwareVersion { type Error = FirmwareVersionError; - fn parse_from_values<'f>( - mut raw_values: impl Iterator, - ) -> Result { - let bytes = raw_values - .next() - .ok_or(FirmwareVersionError::WrongNumberOfFields)?; - - let version = str::from_utf8(bytes)?; - - if raw_values.next().is_some() { - return Err(FirmwareVersionError::WrongNumberOfFields); - } + fn parse_from_values(raw_values: &[bytes::Bytes]) -> Result { + let version = str::from_utf8(&raw_values[0])?.to_string(); + Ok(Self(version)) + } - Ok(Self(version.to_string())) + fn expected_field_count() -> usize { + 1 } } diff --git a/src/command/mod.rs b/src/command/mod.rs index 4b4b7d1..bdd6344 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -2,7 +2,7 @@ pub mod bc125at; mod no_params; mod ok_response; -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; pub(crate) use no_params::NoParams; pub(crate) use ok_response::OkResponse; @@ -27,7 +27,7 @@ pub trait Param { pub trait Response: Sized { type Error: std::error::Error; - fn parse_from_values<'f>( - raw_values: impl Iterator, - ) -> Result; + fn parse_from_values(raw_values: &[Bytes]) -> Result; + + fn expected_field_count() -> usize; } diff --git a/src/command/ok_response.rs b/src/command/ok_response.rs index 944b23f..58dcbe7 100644 --- a/src/command/ok_response.rs +++ b/src/command/ok_response.rs @@ -2,8 +2,8 @@ use crate::command::Response; #[derive(Debug, thiserror::Error)] pub enum OkResponseError { - #[error("expected `OK`, got `{0}`")] - UnexpectedValue(String), + #[error("expected `OK`")] + UnexpectedValue, #[error("expected one response field")] WrongNumberOfFields, } @@ -13,23 +13,15 @@ pub struct OkResponse; impl Response for OkResponse { type Error = OkResponseError; - fn parse_from_values<'f>( - mut raw_values: impl Iterator, - ) -> Result { - let val = raw_values - .next() - .ok_or(OkResponseError::WrongNumberOfFields)?; - - if val != b"OK" { - return Err(OkResponseError::UnexpectedValue( - String::from_utf8_lossy(val).to_string(), - )); - } - - if raw_values.next().is_some() { - return Err(OkResponseError::WrongNumberOfFields); + fn parse_from_values(raw_values: &[bytes::Bytes]) -> Result { + if raw_values[0] != b"OK".as_ref() { + return Err(OkResponseError::UnexpectedValue); } Ok(Self) } + + fn expected_field_count() -> usize { + 1 + } } -- 2.51.2