use core::convert::Infallible;
use core::fmt::Debug;
use std::io::{self, ErrorKind};
use tokio::sync::mpsc::error::SendError;
use tokio::sync::oneshot::error::RecvError;
pub use self::decode::Decode;
pub use self::status::Status;
pub use self::value_error::ValueError;
use crate::frame::parameters::configuration::version;
use crate::frame::parameters::utilities::invalid_command;
use crate::parameters::utilities;
use crate::{Parameters, Response, ember, ezsp};
mod decode;
mod status;
mod value_error;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Decode(#[from] Decode),
#[error(transparent)]
Status(#[from] Status),
#[error("Unexpected response: {0:?}")]
UnexpectedResponse(Box<Parameters>),
#[error(transparent)]
ValueError(#[from] ValueError),
#[error("Invalid command: {0}")]
InvalidCommand(invalid_command::Response),
#[error(
"Protocol negotiation failed: {desired:#04X} (desired) != {negotiated_version:#04X} (negotiated)",
negotiated_version = .negotiated.protocol_version()
)]
ProtocolVersionMismatch {
desired: u8,
negotiated: version::Response,
},
#[error("No matching source endpoint found: {0}")]
NoMatchingSourceEndpoint(u16),
#[error("Receive error: {0}")]
RecvError(#[from] RecvError),
#[error("Send error")]
SendError,
#[error("NCP is not configured")]
NotConfigured,
#[error("Response channel is closed")]
ChannelClosed,
#[error("Transaction queue is full.")]
TransactionQueueFull,
#[error("No endpoints provided.")]
NoEndpoints,
}
impl From<Result<ezsp::Status, u8>> for Error {
fn from(status: Result<ezsp::Status, u8>) -> Self {
Self::Status(status.into())
}
}
impl From<Result<ember::Status, u8>> for Error {
fn from(status: Result<ember::Status, u8>) -> Self {
Self::Status(status.into())
}
}
impl From<Result<silizium::Status, u32>> for Error {
fn from(status: Result<silizium::Status, u32>) -> Self {
Self::Status(status.into())
}
}
impl From<ezsp::Status> for Error {
fn from(status: ezsp::Status) -> Self {
Self::Status(status.into())
}
}
impl From<ember::Status> for Error {
fn from(status: ember::Status) -> Self {
Self::Status(status.into())
}
}
impl From<silizium::Status> for Error {
fn from(status: silizium::Status) -> Self {
Self::Status(status.into())
}
}
impl From<Parameters> for Error {
fn from(parameters: Parameters) -> Self {
if let Parameters::Response(Response::Utilities(utilities::Response::InvalidCommand(
invalid_command,
))) = parameters
{
Self::InvalidCommand(*invalid_command)
} else {
Self::UnexpectedResponse(parameters.into())
}
}
}
impl From<invalid_command::Response> for Error {
fn from(response: invalid_command::Response) -> Self {
Self::InvalidCommand(response)
}
}
impl From<Infallible> for Error {
fn from(infallible: Infallible) -> Self {
match infallible {}
}
}
impl<T> From<SendError<T>> for Error {
fn from(_: SendError<T>) -> Self {
Self::SendError
}
}
impl From<Error> for io::Error {
fn from(error: Error) -> Self {
match error {
Error::Io(error) => error,
Error::Decode(decode) => decode.into(),
Error::Status(status) => status.into(),
Error::UnexpectedResponse(parameters) => Self::new(
ErrorKind::InvalidData,
format!("Unexpected response: {parameters:?}"),
),
Error::ValueError(value_error) => value_error.into(),
Error::InvalidCommand(invalid_command) => invalid_command.into(),
Error::ProtocolVersionMismatch {
desired,
negotiated,
} => Self::new(
ErrorKind::Unsupported,
format!(
"Protocol version mismatch: expected {desired:#04X}, got {:#04X}",
negotiated.protocol_version()
),
),
Error::NoMatchingSourceEndpoint(cluster_id) => Self::new(
ErrorKind::NotFound,
format!("No source endpoint for cluster {cluster_id:#06X}"),
),
Error::RecvError(_) | Error::ChannelClosed => {
Self::new(ErrorKind::BrokenPipe, "Response channel closed")
}
Error::SendError => Self::new(ErrorKind::BrokenPipe, "Request channel closed"),
Error::NotConfigured => Self::new(ErrorKind::NotConnected, "NCP not configured"),
Error::TransactionQueueFull => {
Self::new(ErrorKind::WouldBlock, "Transaction queue full")
}
Error::NoEndpoints => Self::new(ErrorKind::InvalidInput, "No endpoints configured"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const CLUSTER_ID: u16 = 0x1234;
const INVALID_ROUTE_RADIUS: u16 = 0x0100;
const UNKNOWN_STATUS: u8 = 0xFF;
fn assert_conversion<T>(error: T, expected_kind: ErrorKind, expected_message: &str)
where
T: Into<io::Error>,
{
let error = error.into();
assert_eq!(error.kind(), expected_kind);
assert_eq!(error.to_string(), expected_message);
}
#[test]
fn converts_subtypes_to_io_errors() {
assert_conversion(
Decode::TooFewBytes,
ErrorKind::UnexpectedEof,
"Too few bytes to decode.",
);
assert_conversion(
ValueError::InvalidRouteRadius(INVALID_ROUTE_RADIUS),
ErrorKind::InvalidInput,
"Invalid route radius: 256",
);
assert_conversion(
Status::Ezsp(Err(UNKNOWN_STATUS)),
ErrorKind::InvalidData,
"Invalid EZSP status: 0xFF",
);
}
#[test]
fn converts_state_and_capacity_errors() {
assert_conversion(
Error::NoMatchingSourceEndpoint(CLUSTER_ID),
ErrorKind::NotFound,
"No source endpoint for cluster 0x1234",
);
assert_conversion(
Error::NotConfigured,
ErrorKind::NotConnected,
"NCP not configured",
);
assert_conversion(
Error::TransactionQueueFull,
ErrorKind::WouldBlock,
"Transaction queue full",
);
assert_conversion(
Error::NoEndpoints,
ErrorKind::InvalidInput,
"No endpoints configured",
);
}
#[test]
fn converts_closed_channels() {
assert_conversion(
Error::SendError,
ErrorKind::BrokenPipe,
"Request channel closed",
);
assert_conversion(
Error::ChannelClosed,
ErrorKind::BrokenPipe,
"Response channel closed",
);
}
}