use std::fmt;
use std::io;
use super::conn::Conn;
use super::frame::build_frame;
use super::opcode::{stamp_for, Opcode};
use super::uid::DeviceUid;
#[derive(Debug)]
#[non_exhaustive]
pub enum SendError {
ProtocolTooOld {
serial: String,
opcode: u16,
have: u16,
need: u16,
},
NotConnected,
Io(io::Error),
}
impl fmt::Display for SendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ProtocolTooOld {
serial,
opcode,
have,
need,
} => write!(
f,
"{serial} reports protocol {have:#04x}, opcode {opcode:#04x} needs {need:#04x}"
),
Self::NotConnected => write!(f, "not connected"),
Self::Io(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for SendError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for SendError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
pub(crate) trait ProtocolTarget {
fn serial(&self) -> &str;
fn supported_protocol(&self) -> u16;
}
#[derive(Clone, Debug)]
pub(crate) enum Addressee {
Device {
serial: String,
protocol: u16,
},
Broadcast,
}
impl Addressee {
pub(crate) fn device(target: &dyn ProtocolTarget) -> Self {
Self::Device {
serial: target.serial().to_owned(),
protocol: target.supported_protocol(),
}
}
}
#[cfg(test)]
pub(crate) type TxTap = std::sync::Arc<dyn Fn(&[u8]) + Send + Sync>;
#[derive(Default)]
pub(crate) struct Tx {
conn: Option<std::sync::Arc<Conn>>,
#[cfg(test)]
tap: Option<TxTap>,
}
impl Tx {
pub(crate) fn set_conn(&mut self, conn: Option<Conn>) {
self.conn = conn.map(std::sync::Arc::new);
}
pub(crate) fn conn(&self) -> Option<std::sync::Arc<Conn>> {
self.conn.clone()
}
#[cfg(test)]
pub(crate) fn set_tap(&mut self, tap: TxTap) {
self.tap = Some(tap);
}
pub(crate) fn send(
&self,
to: &Addressee,
uid: DeviceUid,
opcode: Opcode,
payload: &[u8],
) -> Result<usize, SendError> {
let need = stamp_for(opcode);
if let Addressee::Device { serial, protocol } = to {
if *protocol != 0 && *protocol < need {
return Err(SendError::ProtocolTooOld {
serial: serial.clone(),
opcode: opcode.0,
have: *protocol,
need,
});
}
}
let frame = build_frame(uid, opcode, need, payload);
#[cfg(test)]
if let Some(tap) = &self.tap {
tap(&frame);
}
let conn = self.conn.as_ref().ok_or(SendError::NotConnected)?;
Ok(conn.send(&frame)?)
}
}