use std::fmt;
use std::io;
use std::num::Wrapping;
use crate::Result;
pub trait Command {
type Response;
fn execute<T: io::Read + io::Write>(&self, p: &mut T) -> Result<Self::Response>;
}
pub trait Transmit {
fn tx<T: io::Write>(&self, p: &mut T) -> Result<()>;
}
pub struct CommandData {
pub opcode: u8,
pub has_size_field: bool,
pub payload: Vec<u8>,
}
impl CommandData {
fn bytes(&self) -> Vec<u8> {
let mut bytes = vec![];
let payload = &self.payload;
let payload_size = payload.len();
bytes.push(self.opcode);
if self.has_size_field {
bytes.push(payload_size as u8);
}
bytes.extend(payload);
if payload_size != 0 {
let sum = bytes.iter().map(|x| Wrapping(*x)).sum::<Wrapping<u8>>();
let checksum = !sum + Wrapping::<u8>(1);
bytes.push(checksum.0);
}
bytes
}
}
pub trait TransmitCommandData {
fn command_data(&self) -> CommandData;
}
impl<T: TransmitCommandData> Transmit for T {
fn tx<U: io::Write>(&self, p: &mut U) -> Result<()> {
p.write(&self.command_data().bytes())?;
p.flush()?;
Ok(())
}
}
pub trait Receive {
type Response;
fn rx<T: io::Read>(&self, p: &mut T) -> Result<Self::Response>;
}
impl<T: Transmit + Receive> Command for T {
type Response = T::Response;
fn execute<U: io::Read + io::Write>(&self, p: &mut U) -> Result<Self::Response> {
self.tx(p)?;
self.rx(p)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum CommandError {
Address,
BitRateSelection,
BlockNumber,
Checksum,
ClockMode,
DataSize,
DeviceCode,
Erasure,
IDCodeMismatch,
InputFrequency,
MultiplicationRatio,
OperatingFrequency,
Programming,
ProgrammingErasureStateTransition,
}
impl fmt::Display for CommandError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
CommandError::Address => "invalid address/area",
CommandError::BitRateSelection => "bit rate selection error too high",
CommandError::BlockNumber => "invalid block number",
CommandError::Checksum => "checksum mismatch",
CommandError::ClockMode => "invalid clock mode",
CommandError::DataSize => "invalid data size",
CommandError::DeviceCode => "invalid device code",
CommandError::Erasure => "erasure error",
CommandError::IDCodeMismatch => "ID code mismatch",
CommandError::InputFrequency => "input frequency out of range",
CommandError::MultiplicationRatio => "invalid multiplication ratio",
CommandError::OperatingFrequency => "calculated operating frequency out of range",
CommandError::Programming => "programming error",
CommandError::ProgrammingErasureStateTransition => {
"failed to transition into programming/erasure state"
}
}
)
}
}