use crate::buffer::Buffer;
use crate::crc::{crc16_xmodem, crc32_iso_hdlc};
use crate::error::Error;
use crate::io::Write;
use crate::zdle;
use crate::{XON, ZDLE, ZPAD};
use bitflags::bitflags;
pub(crate) const HEADER_SIZE: usize = 32;
pub(crate) 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(Clone, Copy, Debug, 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,
}
}
#[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(crate) 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(()))
}
#[must_use]
pub const fn with_count(self, count: u32) -> Self {
Header::new(self.encoding, self.frame, count.to_le_bytes())
}
pub(crate) 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
}
}
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)
}