use heapless::Vec;
use super::SdoPayload;
use crate::object_dictionary::Address;
use crate::{Error, Result};
const CS_6: u8 = 0xC0;
const CS_5: u8 = 0xA0;
const CS_MASK: u8 = 0xE0;
const CRC_SUPPORTED: u8 = 0x04; const SIZE_INDICATED: u8 = 0x02;
const SUBCMD_MASK: u8 = 0x03;
const SUBCMD_INITIATE: u8 = 0x00;
const SUBCMD_END: u8 = 0x01;
const SUBCMD_RESPONSE: u8 = 0x02;
const SUBCMD_START: u8 = 0x03;
const LAST_SEGMENT: u8 = 0x80; const SEQNO_MASK: u8 = 0x7F;
pub const SEGMENT_DATA_MAX: usize = 7;
pub const MAX_BLKSIZE: u8 = 127;
pub fn crc16(data: &[u8]) -> u16 {
let mut crc: u16 = 0;
for &byte in data {
crc ^= (byte as u16) << 8;
for _ in 0..8 {
crc = if crc & 0x8000 != 0 {
(crc << 1) ^ 0x1021
} else {
crc << 1
};
}
}
crc
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockInitiate {
pub address: Address,
pub size: Option<u32>,
pub crc_support: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockNegotiation {
pub address: Address,
pub blksize: u8,
pub crc_support: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubSegment<'a> {
pub seqno: u8,
pub last: bool,
pub data: &'a [u8],
}
fn address_of(p: &SdoPayload) -> Address {
Address::new(u16::from_le_bytes([p[1], p[2]]), p[3])
}
fn put_address(p: &mut SdoPayload, addr: Address) {
p[1..3].copy_from_slice(&addr.index.to_le_bytes());
p[3] = addr.subindex;
}
pub fn encode_download_initiate(addr: Address, size: Option<u32>, crc_support: bool) -> SdoPayload {
encode_initiate_with_size(CS_6, addr, size, crc_support)
}
pub fn decode_download_initiate(p: &SdoPayload) -> Result<BlockInitiate> {
decode_initiate_with_size(CS_6, p)
}
pub fn encode_download_initiate_response(
addr: Address,
blksize: u8,
crc_support: bool,
) -> SdoPayload {
encode_initiate_with_blksize(CS_5, addr, blksize, crc_support)
}
pub fn decode_download_initiate_response(p: &SdoPayload) -> Result<BlockNegotiation> {
decode_initiate_with_blksize(CS_5, p)
}
pub fn encode_upload_initiate(addr: Address, blksize: u8, crc_support: bool) -> SdoPayload {
encode_initiate_with_blksize(CS_5, addr, blksize, crc_support)
}
pub fn decode_upload_initiate(p: &SdoPayload) -> Result<BlockNegotiation> {
decode_initiate_with_blksize(CS_5, p)
}
pub fn encode_upload_initiate_response(
addr: Address,
size: Option<u32>,
crc_support: bool,
) -> SdoPayload {
encode_initiate_with_size(CS_6, addr, size, crc_support)
}
pub fn decode_upload_initiate_response(p: &SdoPayload) -> Result<BlockInitiate> {
decode_initiate_with_size(CS_6, p)
}
pub fn encode_upload_start() -> SdoPayload {
let mut p = [0u8; 8];
p[0] = CS_5 | SUBCMD_START;
p
}
pub fn decode_upload_start(p: &SdoPayload) -> Result<()> {
if p[0] & CS_MASK != CS_5 || p[0] & SUBCMD_MASK != SUBCMD_START {
return Err(Error::UnexpectedCommand);
}
Ok(())
}
fn encode_initiate_with_size(cs: u8, addr: Address, size: Option<u32>, crc: bool) -> SdoPayload {
let mut p = [0u8; 8];
p[0] = cs | SUBCMD_INITIATE;
if crc {
p[0] |= CRC_SUPPORTED;
}
if let Some(size) = size {
p[0] |= SIZE_INDICATED;
p[4..8].copy_from_slice(&size.to_le_bytes());
}
put_address(&mut p, addr);
p
}
fn decode_initiate_with_size(cs: u8, p: &SdoPayload) -> Result<BlockInitiate> {
if p[0] & CS_MASK != cs || p[0] & SUBCMD_END != 0 {
return Err(Error::UnexpectedCommand);
}
let size = (p[0] & SIZE_INDICATED != 0).then(|| u32::from_le_bytes([p[4], p[5], p[6], p[7]]));
Ok(BlockInitiate {
address: address_of(p),
size,
crc_support: p[0] & CRC_SUPPORTED != 0,
})
}
fn encode_initiate_with_blksize(cs: u8, addr: Address, blksize: u8, crc: bool) -> SdoPayload {
let mut p = [0u8; 8];
p[0] = cs | SUBCMD_INITIATE;
if crc {
p[0] |= CRC_SUPPORTED;
}
put_address(&mut p, addr);
p[4] = blksize;
p
}
fn decode_initiate_with_blksize(cs: u8, p: &SdoPayload) -> Result<BlockNegotiation> {
if p[0] & CS_MASK != cs || p[0] & SUBCMD_MASK != SUBCMD_INITIATE {
return Err(Error::UnexpectedCommand);
}
Ok(BlockNegotiation {
address: address_of(p),
blksize: p[4],
crc_support: p[0] & CRC_SUPPORTED != 0,
})
}
pub fn encode_sub_segment(seqno: u8, data: &[u8], last: bool) -> Result<SdoPayload> {
if seqno == 0 || seqno > MAX_BLKSIZE || data.is_empty() || data.len() > SEGMENT_DATA_MAX {
return Err(Error::BadLength);
}
let mut p = [0u8; 8];
p[0] = seqno;
if last {
p[0] |= LAST_SEGMENT;
}
p[1..1 + data.len()].copy_from_slice(data);
Ok(p)
}
pub fn decode_sub_segment(p: &SdoPayload) -> SubSegment<'_> {
SubSegment {
seqno: p[0] & SEQNO_MASK,
last: p[0] & LAST_SEGMENT != 0,
data: &p[1..8],
}
}
pub fn encode_sub_response(ackseq: u8, blksize: u8) -> SdoPayload {
let mut p = [0u8; 8];
p[0] = CS_5 | SUBCMD_RESPONSE;
p[1] = ackseq;
p[2] = blksize;
p
}
pub fn decode_sub_response(p: &SdoPayload) -> Result<(u8, u8)> {
if p[0] & CS_MASK != CS_5 || p[0] & SUBCMD_MASK != SUBCMD_RESPONSE {
return Err(Error::UnexpectedCommand);
}
Ok((p[1], p[2]))
}
pub fn encode_end(unused: u8, crc: u16) -> SdoPayload {
let mut p = [0u8; 8];
p[0] = CS_6 | ((unused & 0x07) << 2) | SUBCMD_END;
p[1..3].copy_from_slice(&crc.to_le_bytes());
p
}
pub fn decode_end(p: &SdoPayload) -> Result<(u8, u16)> {
if p[0] & CS_MASK != CS_6 || p[0] & SUBCMD_MASK != SUBCMD_END {
return Err(Error::UnexpectedCommand);
}
Ok(((p[0] >> 2) & 0x07, u16::from_le_bytes([p[1], p[2]])))
}
pub fn encode_end_response() -> SdoPayload {
let mut p = [0u8; 8];
p[0] = CS_5 | SUBCMD_END;
p
}
pub fn decode_end_response(p: &SdoPayload) -> Result<()> {
if p[0] & CS_MASK != CS_5 || p[0] & SUBCMD_MASK != SUBCMD_END {
return Err(Error::UnexpectedCommand);
}
Ok(())
}
#[derive(Debug)]
pub struct BlockWriter<'a> {
data: &'a [u8],
pos: usize,
seqno: u8,
blksize: u8,
}
impl<'a> BlockWriter<'a> {
pub fn new(data: &'a [u8], blksize: u8) -> Self {
Self {
data,
pos: 0,
seqno: 1,
blksize,
}
}
pub const fn is_done(&self) -> bool {
self.pos >= self.data.len()
}
pub fn next_segment(&mut self) -> Option<SdoPayload> {
if self.is_done() || self.seqno > self.blksize {
return None;
}
let remaining = self.data.len() - self.pos;
let take = remaining.min(SEGMENT_DATA_MAX);
let last = remaining <= SEGMENT_DATA_MAX;
let segment = encode_sub_segment(self.seqno, &self.data[self.pos..self.pos + take], last)
.expect("seqno and length are in range");
self.pos += take;
self.seqno += 1;
Some(segment)
}
pub fn start_sub_block(&mut self, blksize: u8) {
self.seqno = 1;
self.blksize = blksize;
}
pub fn end_frame(&self, crc_support: bool) -> SdoPayload {
let last_len = match self.data.len() % SEGMENT_DATA_MAX {
0 if !self.data.is_empty() => SEGMENT_DATA_MAX,
r => r,
};
let unused = (SEGMENT_DATA_MAX - last_len) as u8;
let crc = if crc_support { crc16(self.data) } else { 0 };
encode_end(unused, crc)
}
}
#[derive(Debug)]
pub struct BlockReceiver<const N: usize> {
buf: Vec<u8, N>,
done: bool,
}
impl<const N: usize> Default for BlockReceiver<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> BlockReceiver<N> {
pub const fn new() -> Self {
Self {
buf: Vec::new(),
done: false,
}
}
pub const fn is_done(&self) -> bool {
self.done
}
pub fn push(&mut self, segment: &SubSegment) -> Result<()> {
if self.done {
return Err(Error::UnexpectedCommand);
}
self.buf
.extend_from_slice(segment.data)
.map_err(|_| Error::Overflow)?;
if segment.last {
self.done = true;
}
Ok(())
}
pub fn finish(&mut self, unused: u8, crc: u16, verify_crc: bool) -> Result<&[u8]> {
let unused = unused as usize;
if unused > self.buf.len() {
return Err(Error::BadLength);
}
self.buf.truncate(self.buf.len() - unused);
if verify_crc && crc16(&self.buf) != crc {
return Err(Error::CrcMismatch);
}
Ok(&self.buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc16_known_vector() {
assert_eq!(crc16(b"123456789"), 0x31C3); assert_eq!(crc16(&[]), 0x0000);
}
#[test]
fn download_initiate_frames() {
let req = encode_download_initiate(Address::new(0x2000, 0), Some(100), true);
assert_eq!(req, [0xC6, 0x00, 0x20, 0x00, 100, 0, 0, 0]);
assert_eq!(
decode_download_initiate(&req).unwrap(),
BlockInitiate {
address: Address::new(0x2000, 0),
size: Some(100),
crc_support: true
}
);
let resp = encode_download_initiate_response(Address::new(0x2000, 0), 10, true);
assert_eq!(resp, [0xA4, 0x00, 0x20, 0x00, 10, 0, 0, 0]);
assert_eq!(
decode_download_initiate_response(&resp).unwrap(),
BlockNegotiation {
address: Address::new(0x2000, 0),
blksize: 10,
crc_support: true
}
);
}
#[test]
fn upload_initiate_and_start_frames() {
let req = encode_upload_initiate(Address::new(0x2000, 0), 5, false);
assert_eq!(req, [0xA0, 0x00, 0x20, 0x00, 5, 0, 0, 0]);
assert_eq!(decode_upload_initiate(&req).unwrap().blksize, 5);
let resp = encode_upload_initiate_response(Address::new(0x2000, 0), Some(42), true);
assert_eq!(resp, [0xC6, 0x00, 0x20, 0x00, 42, 0, 0, 0]);
assert_eq!(
decode_upload_initiate_response(&resp).unwrap().size,
Some(42)
);
assert_eq!(encode_upload_start(), [0xA3, 0, 0, 0, 0, 0, 0, 0]);
assert!(decode_upload_start(&encode_upload_start()).is_ok());
}
#[test]
fn shared_segment_and_ack_frames() {
let f = encode_sub_segment(1, &[1, 2, 3, 4, 5, 6, 7], false).unwrap();
assert_eq!(f, [0x01, 1, 2, 3, 4, 5, 6, 7]);
let last = encode_sub_segment(3, &[0xAA, 0xBB], true).unwrap();
assert_eq!(last, [0x83, 0xAA, 0xBB, 0, 0, 0, 0, 0]);
assert!(decode_sub_segment(&last).last);
assert_eq!(encode_sub_response(10, 20), [0xA2, 10, 20, 0, 0, 0, 0, 0]);
assert_eq!(
decode_sub_response(&encode_sub_response(10, 20)).unwrap(),
(10, 20)
);
assert_eq!(encode_end(5, 0xBEEF), [0xD5, 0xEF, 0xBE, 0, 0, 0, 0, 0]);
assert_eq!(decode_end(&encode_end(5, 0xBEEF)).unwrap(), (5, 0xBEEF));
assert_eq!(encode_end_response(), [0xA1, 0, 0, 0, 0, 0, 0, 0]);
assert!(decode_end_response(&encode_end_response()).is_ok());
}
fn roundtrip(data: &[u8], blksize: u8) {
let mut writer = BlockWriter::new(data, blksize);
let mut receiver = BlockReceiver::<128>::new();
loop {
while let Some(seg) = writer.next_segment() {
receiver.push(&decode_sub_segment(&seg)).unwrap();
}
if writer.is_done() {
break;
}
writer.start_sub_block(blksize);
}
assert!(receiver.is_done());
let (unused, crc) = decode_end(&writer.end_frame(true)).unwrap();
assert_eq!(receiver.finish(unused, crc, true).unwrap(), data);
}
#[test]
fn block_transfer_roundtrips() {
let data: [u8; 30] = core::array::from_fn(|i| i as u8);
roundtrip(&data, 2); roundtrip(&data, 127); roundtrip(&[0xAB; 7], 4); }
#[test]
fn crc_mismatch_is_detected() {
let data = [1u8, 2, 3, 4, 5];
let mut writer = BlockWriter::new(&data, 1);
let mut receiver = BlockReceiver::<16>::new();
while let Some(seg) = writer.next_segment() {
receiver.push(&decode_sub_segment(&seg)).unwrap();
}
let (unused, _) = decode_end(&writer.end_frame(true)).unwrap();
assert_eq!(
receiver.finish(unused, 0x0000, true),
Err(Error::CrcMismatch)
);
}
}