use ashv2::Payload;
use le_stream::FromLeStream;
use log::{trace, warn};
use tokio::sync::mpsc;
use crate::error::Decode;
use crate::frame::parsable::WarnExcessBytes;
use crate::parameters::utilities::invalid_command;
use crate::{
Error, Extended, Frame, Header, Legacy, LowByte, MIN_NON_LEGACY_VERSION, Parameters, Parsable,
Receive, ezsp,
};
pub struct AshRx {
inbox: mpsc::Receiver<Payload>,
negotiated_version: Option<u8>,
}
impl AshRx {
#[must_use]
pub const fn new(inbox: mpsc::Receiver<Payload>) -> Self {
Self {
inbox,
negotiated_version: None,
}
}
fn read_header<T>(&self, stream: T) -> Option<Header>
where
T: Iterator<Item = u8>,
{
if self
.negotiated_version
.is_some_and(|version| version >= MIN_NON_LEGACY_VERSION.get())
{
Extended::from_le_stream(stream).map(Header::Extended)
} else {
Legacy::from_le_stream(stream).map(Header::Legacy)
}
}
fn parse_frame(&self, frame: Payload) -> Result<Frame<Parameters>, Error> {
trace!("Decoding ASHv2 frame: {frame:#04X?}");
let mut stream = frame.into_iter();
let header = self.read_header(&mut stream).ok_or(Decode::TooFewBytes)?;
trace!("Decoded header: {header}");
if let LowByte::Response(response) = header.low_byte() {
if response.is_truncated() {
return Err(ezsp::Status::Error(ezsp::Error::Truncated).into());
}
if response.has_overflowed() {
return Err(ezsp::Status::Error(ezsp::Error::Overflow).into());
}
}
trace!("Accumulated parameters: {stream:#04X?}");
if header.id() == invalid_command::Response::ID {
return Err(invalid_command::Response::from_le_stream_exact(&mut stream)
.warn_excess_bytes(stream)?
.into());
}
match Parameters::parse_from_le_stream(header.id(), stream) {
Ok(parameters) => {
trace!("Decoded parameters: {parameters:?}");
Ok(Frame::new(header, parameters))
}
Err(error) => Err(error.into()),
}
}
}
impl Receive for AshRx {
async fn receive(&mut self) -> Option<Frame<Parameters>> {
loop {
let frame = self.inbox.recv().await?;
match self.parse_frame(frame) {
Ok(frame) => return Some(frame),
Err(error) => {
warn!("{error}");
}
}
}
}
fn set_negotiated_version(&mut self, version: u8) -> Option<u8> {
self.negotiated_version.replace(version)
}
}