use crate::buffer::Buffer;
use crate::crc::{crc16_xmodem, crc32_iso_hdlc};
use crate::error::Error;
use crate::io::{Read, Write};
use crate::zdle;
use crate::{XON, ZDLE, ZPAD};
use bitflags::bitflags;
const HEADER_SIZE: usize = 32;
const HEADER_PAYLOAD_SIZE: usize = 5;
pub(crate) const ZACK_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZACK, &[0; 4]);
pub(crate) const ZDATA_HEADER: Header = Header::new(Encoding::ZBIN32, Frame::ZDATA, &[0; 4]);
pub(crate) const ZEOF_HEADER: Header = Header::new(Encoding::ZBIN32, Frame::ZEOF, &[0; 4]);
pub(crate) const ZFIN_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZFIN, &[0; 4]);
pub(crate) const ZNAK_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZNAK, &[0; 4]);
pub(crate) const ZRPOS_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZRPOS, &[0; 4]);
pub(crate) const ZRQINIT_HEADER: Header = Header::new(Encoding::ZHEX, Frame::ZRQINIT, &[0; 4]);
#[repr(C)]
#[derive(PartialEq)]
pub struct Header {
encoding: Encoding,
frame: Frame,
flags: [u8; 4],
}
impl Header {
#[must_use]
pub const fn new(encoding: Encoding, frame: Frame, flags: &[u8; 4]) -> Self {
Self {
encoding,
frame,
flags: *flags,
}
}
#[must_use]
pub const fn encoding(&self) -> Encoding {
self.encoding
}
#[must_use]
pub const fn frame(&self) -> Frame {
self.frame
}
#[must_use]
pub const fn count(&self) -> u32 {
u32::from_le_bytes(self.flags)
}
pub fn write<P>(&self, port: &mut P) -> Result<Option<()>, Error>
where
P: Write + ?Sized,
{
if write_header_start(port, self.encoding)?.is_none() {
return Ok(None);
}
let mut out: Buffer<HEADER_SIZE> = Buffer::new();
out.push(self.frame as u8).map_err(|_| Error::OutOfMemory)?;
out.extend_from_slice(&self.flags)
.map_err(|_| Error::OutOfMemory)?;
let mut crc = [0u8; 4];
let crc_len = make_crc(&out, &mut crc, self.encoding);
out.extend_from_slice(&crc[..crc_len])
.map_err(|_| Error::OutOfMemory)?;
if self.encoding == Encoding::ZHEX {
let mut hex_buf = [0u8; HEADER_SIZE];
let len = out.len() * 2;
let hex = &mut hex_buf.get_mut(..len).ok_or(Error::UnexpectedEof)?;
hex::encode_to_slice(&out, hex).map_err(|_| Error::OutOfMemory)?;
if write_slice_escaped(port, hex)?.is_none() {
return Ok(None);
}
if write_header_end_hex(port, self.frame)?.is_none() {
return Ok(None);
}
} else if write_slice_escaped(port, &out)?.is_none() {
return Ok(None);
}
Ok(Some(()))
}
pub fn read<P>(port: &mut P) -> Result<Option<Header>, Error>
where
P: Read + ?Sized,
{
let Some(encoding_byte) = port.read_byte()? else {
return Ok(None);
};
let encoding = Encoding::try_from(encoding_byte)?;
let mut out_hex: Buffer<HEADER_SIZE> = Buffer::new();
for _ in 0..Header::read_size(encoding) {
let Some(byte) = read_byte_unescaped(port)? else {
return Ok(None);
};
out_hex.push(byte).map_err(|_| Error::OutOfMemory)?;
}
let mut out: Buffer<HEADER_SIZE> = Buffer::new();
if encoding == Encoding::ZHEX {
let mut out_bytes = [0u8; HEADER_SIZE / 2];
let out_len = out_hex.len() / 2;
hex::decode_to_slice(&out_hex, &mut out_bytes[..out_len])
.map_err(|_| Error::MalformedHeader)?;
out.extend_from_slice(&out_bytes[..out_len])
.map_err(|_| Error::OutOfMemory)?;
} else {
out.extend_from_slice(&out_hex)
.map_err(|_| Error::OutOfMemory)?;
}
check_crc(&out[..5], &out[5..], encoding)?;
let frame = Frame::try_from(out[0])?;
let mut header = Header::new(encoding, frame, &[0; 4]);
header.flags.copy_from_slice(&out[1..=4]);
Ok(Some(header))
}
#[must_use]
pub const fn with_count(&self, count: u32) -> Self {
Header::new(self.encoding, self.frame, &count.to_le_bytes())
}
const fn read_size(encoding: Encoding) -> usize {
match encoding {
Encoding::ZBIN => HEADER_PAYLOAD_SIZE + 2,
Encoding::ZBIN32 => HEADER_PAYLOAD_SIZE + 4,
Encoding::ZHEX => (HEADER_PAYLOAD_SIZE + 2) * 2,
}
}
}
#[repr(u8)]
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Encoding {
ZBIN = 0x41,
ZHEX = 0x42,
ZBIN32 = 0x43,
}
impl TryFrom<u8> for Encoding {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x41 => Ok(Encoding::ZBIN),
0x42 => Ok(Encoding::ZHEX),
0x43 => Ok(Encoding::ZBIN32),
_ => Err(Error::MalformedEncoding(value)),
}
}
}
#[repr(u8)]
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Frame {
ZRQINIT = 0,
ZRINIT = 1,
ZSINIT = 2,
ZACK = 3,
ZFILE = 4,
ZSKIP = 5,
ZNAK = 6,
ZABORT = 7,
ZFIN = 8,
ZRPOS = 9,
ZDATA = 10,
ZEOF = 11,
ZFERR = 12,
ZCRC = 13,
ZCHALLENGE = 14,
ZCOMPL = 15,
ZCAN = 16,
ZFREECNT = 17,
ZCOMMAND = 18,
ZSTDERR = 19,
}
impl TryFrom<u8> for Frame {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Frame::ZRQINIT),
1 => Ok(Frame::ZRINIT),
2 => Ok(Frame::ZSINIT),
3 => Ok(Frame::ZACK),
4 => Ok(Frame::ZFILE),
5 => Ok(Frame::ZSKIP),
6 => Ok(Frame::ZNAK),
7 => Ok(Frame::ZABORT),
8 => Ok(Frame::ZFIN),
9 => Ok(Frame::ZRPOS),
10 => Ok(Frame::ZDATA),
11 => Ok(Frame::ZEOF),
12 => Ok(Frame::ZFERR),
13 => Ok(Frame::ZCRC),
14 => Ok(Frame::ZCHALLENGE),
15 => Ok(Frame::ZCOMPL),
16 => Ok(Frame::ZCAN),
17 => Ok(Frame::ZFREECNT),
18 => Ok(Frame::ZCOMMAND),
19 => Ok(Frame::ZSTDERR),
_ => Err(Error::MalformedFrame(value)),
}
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Zrinit: u8 {
const CANFDX = 0x01;
const CANOVIO = 0x02;
const CANBRK = 0x04;
const CANCRY = 0x08;
const CANLZW = 0x10;
const CANFC32 = 0x20;
const ESCCTL = 0x40;
const ESC8 = 0x80;
}
}
fn write_header_start<P>(port: &mut P, encoding: Encoding) -> Result<Option<()>, Error>
where
P: Write + ?Sized,
{
if port.write_byte(ZPAD)?.is_none() {
return Ok(None);
}
if encoding == Encoding::ZHEX && port.write_byte(ZPAD)?.is_none() {
return Ok(None);
}
if port.write_byte(ZDLE)?.is_none() {
return Ok(None);
}
port.write_byte(encoding as u8)
}
fn write_header_end_hex<P>(port: &mut P, frame: Frame) -> Result<Option<()>, Error>
where
P: Write + ?Sized,
{
if port.write_byte(b'\r')?.is_none() {
return Ok(None);
}
if port.write_byte(b'\n')?.is_none() {
return Ok(None);
}
if frame != Frame::ZACK && frame != Frame::ZFIN && port.write_byte(XON)?.is_none() {
return Ok(None);
}
Ok(Some(()))
}
fn make_crc(data: &[u8], out: &mut [u8], encoding: Encoding) -> usize {
if encoding == Encoding::ZBIN32 {
let crc = crc32_iso_hdlc(data).to_le_bytes();
out[..4].copy_from_slice(&crc[..4]);
4
} else {
let crc = crc16_xmodem(data).to_be_bytes();
out[..2].copy_from_slice(&crc[..2]);
2
}
}
fn check_crc(data: &[u8], crc: &[u8], encoding: Encoding) -> Result<(), Error> {
let mut crc2 = [0u8; 4];
let crc2_len = make_crc(data, &mut crc2, encoding);
if *crc == crc2[..crc2_len] {
Ok(())
} else if encoding == Encoding::ZBIN32 {
Err(Error::UnexpectedCrc32)
} else {
Err(Error::UnexpectedCrc16)
}
}
pub(crate) fn write_slice_escaped<P>(port: &mut P, buf: &[u8]) -> Result<Option<()>, Error>
where
P: Write + ?Sized,
{
for value in buf {
if write_byte_escaped(port, *value)?.is_none() {
return Ok(None);
}
}
Ok(Some(()))
}
pub(crate) fn write_byte_escaped<P>(port: &mut P, value: u8) -> Result<Option<()>, Error>
where
P: Write + ?Sized,
{
let escaped = zdle::ZDLE_TABLE[value as usize];
if escaped != value && port.write_byte(ZDLE)?.is_none() {
return Ok(None);
}
port.write_byte(escaped)
}
pub(crate) fn read_byte_unescaped<P>(port: &mut P) -> Result<Option<u8>, Error>
where
P: Read + ?Sized,
{
let Some(b) = port.read_byte()? else {
return Ok(None);
};
Ok(Some(if b == ZDLE {
let Some(b) = port.read_byte()? else {
return Ok(None);
};
zdle::UNZDLE_TABLE[b as usize]
} else {
b
}))
}