use crate::Constants;
use crate::FrameType;
#[cfg(feature = "std")]
use crate::Slipmux;
use crate::checksum::CONF_FCS16;
use crate::checksum::fcs16_finish;
use crate::checksum::fcs16_part;
use serial_line_ip::Encoder;
#[must_use]
pub fn encode_diagnostic(text: &str, buffer: &mut [u8]) -> usize {
encode(FrameType::Diagnostic, text.as_bytes(), buffer)
}
#[must_use]
pub fn encode_configuration(packet: &[u8], buffer: &mut [u8]) -> usize {
encode(FrameType::Configuration, packet, buffer)
}
#[must_use]
pub fn encode_packet(packet: &[u8], buffer: &mut [u8]) -> usize {
encode(FrameType::Ip, packet, buffer)
}
#[cfg(feature = "std")]
#[must_use]
pub fn encode_buffered(input: Slipmux) -> Vec<u8> {
const fn space_requirement(data_len: usize) -> usize {
const FRAMETYPE_BYTE: usize = 1;
const ENDFRAME_BYTE: usize = 1;
let max_encoded_len: usize = data_len * 2;
ENDFRAME_BYTE + FRAMETYPE_BYTE + max_encoded_len + ENDFRAME_BYTE
}
let mut buffer: Vec<u8> = vec![];
let length = match input {
Slipmux::Diagnostic(s) => {
buffer.resize(space_requirement(s.len()), 0);
encode(FrameType::Diagnostic, s.as_bytes(), &mut buffer)
}
Slipmux::Configuration(conf) => {
const CHECKSUM_BYTES: usize = 2;
buffer.resize(space_requirement(conf.len() + CHECKSUM_BYTES), 0);
encode(FrameType::Configuration, &conf, &mut buffer)
}
Slipmux::Packet(packet) => {
buffer.resize(space_requirement(packet.len()), 0);
encode(FrameType::Ip, &packet, &mut buffer)
}
};
buffer.truncate(length);
buffer
}
#[must_use]
pub fn encode(ftype: FrameType, data: &[u8], buffer: &mut [u8]) -> usize {
let mut encoder = ChunkedEncoder::new(ftype, data);
let size = encoder.encode_chunk(buffer);
assert!(encoder.is_exhausted(), "Output buffer was too small");
size
}
pub struct ChunkedEncoder<'input> {
header: &'static [u8],
data: &'input [u8],
fcs_cursor: u8,
fcs: [u8; 2],
slip: Option<Encoder>,
}
impl<'input> ChunkedEncoder<'input> {
#[must_use]
pub fn new(ftype: FrameType, data: &'input [u8]) -> Self {
let header: &[u8] = match ftype {
FrameType::Diagnostic => &[Constants::DIAGNOSTIC],
FrameType::Configuration => &[Constants::CONFIGURATION],
FrameType::Ip => &[],
};
let (fcs, fcs_cursor) = if matches!(ftype, FrameType::Configuration) {
let fcs = fcs16_part(CONF_FCS16, data);
(fcs16_finish(fcs).to_le_bytes(), 0)
} else {
([0, 0], 2)
};
Self {
header,
data,
fcs_cursor,
fcs,
slip: Some(Encoder::new()),
}
}
fn slice_to_encode(&self) -> Option<&[u8]> {
if !self.header.is_empty() {
Some(self.header)
} else if !self.data.is_empty() {
Some(self.data)
} else if (self.fcs_cursor as usize) < self.fcs.len() {
Some(&self.fcs[self.fcs_cursor as usize..])
} else {
None
}
}
fn advance_slice(&mut self, amount: usize) {
if !self.header.is_empty() {
self.header = &self.header[amount..];
} else if !self.data.is_empty() {
self.data = &self.data[amount..];
} else {
self.fcs_cursor += amount as u8;
}
}
pub fn encode_chunk(&mut self, mut buffer: &mut [u8]) -> usize {
assert!(buffer.len() >= 2, "Chunk too short for minimal progress.");
let buffer_len_initial = buffer.len();
let Some(mut slip) = self.slip.take() else {
return 0;
};
loop {
if let Some(slice) = self.slice_to_encode() {
let encoded = slip.encode(slice, buffer).expect(
"this only fails when there is not even enough room for the start byte",
);
buffer = &mut buffer[encoded.written..];
self.advance_slice(encoded.read);
if encoded.written == 0 {
break;
}
} else {
#[allow(
clippy::redundant_else,
clippy::if_not_else,
reason = "reflects logical decision tree"
)]
if !buffer.is_empty() {
let encoded = slip.finish(buffer).expect("buffer was checked explictly");
return buffer_len_initial - buffer.len() + encoded.written;
} else {
break;
}
}
}
self.slip = Some(slip);
buffer_len_initial - buffer.len()
}
#[must_use]
#[expect(
clippy::missing_const_for_fn,
reason = "no point in this for runtime state"
)]
pub fn is_exhausted(&self) -> bool {
self.slip.is_none()
}
}
#[cfg(test)]
mod tests {
use super::*;
use coap_lite::Packet;
#[test]
#[cfg(feature = "std")]
fn simple_diagnostic() {
let mut buffer: [u8; 2048] = [0; 2048];
let length = encode_diagnostic("Hello World!", &mut buffer);
assert_eq!(buffer[..length], *b"\xc0\x0aHello World!\xc0");
let length = encode_diagnostic("Yes, I would like one \x0a please.", &mut buffer);
assert_eq!(
buffer[..length],
*b"\xc0\x0aYes, I would like one \x0a please.\xc0"
);
}
#[test]
#[cfg(feature = "std")]
fn wrapper_diagnostic() {
let mut buffer: [u8; 2048] = [0; 2048];
let length = encode_diagnostic("", &mut buffer);
assert_eq!(buffer[..length], *b"\xc0\x0a\xc0");
}
#[test]
#[cfg(feature = "std")]
fn wrapper_configuration() {
let mut buffer: [u8; 2048] = [0; 2048];
let length = encode_configuration(&Packet::new().to_bytes().unwrap(), &mut buffer);
assert_eq!(
buffer[..length],
[
Constants::END,
Constants::CONFIGURATION,
0x40,
0x01,
0x00,
0x00,
0xbc,
0x38,
Constants::END
]
);
}
#[test]
fn direct() {
const DATA: &str = "Hello World!";
let mut buffer: [u8; 2048] = [0; 2048];
let length = encode(FrameType::Diagnostic, DATA.as_bytes(), &mut buffer);
assert_eq!(buffer[..length], *b"\xc0\x0aHello World!\xc0");
let packet: &[u8] = &Packet::new().to_bytes().unwrap();
let length = encode(FrameType::Configuration, packet, &mut buffer);
assert_eq!(
buffer[..length],
[
Constants::END,
Constants::CONFIGURATION,
0x40,
0x01,
0x00,
0x00,
0xbc,
0x38,
Constants::END
]
);
}
fn chunked<const N: usize>() {
extern crate alloc;
use alloc::vec::Vec;
const DATA: &str = "Hello World!";
let mut encoder = ChunkedEncoder::new(FrameType::Diagnostic, DATA.as_bytes());
let mut output = Vec::new();
while !encoder.is_exhausted() {
let mut buf = [0; N];
let length = encoder.encode_chunk(&mut buf);
output.extend_from_slice(&buf[..length]);
}
assert_eq!(output, *b"\xc0\x0aHello World!\xc0");
let packet: &[u8] = &Packet::new().to_bytes().unwrap();
let mut encoder = ChunkedEncoder::new(FrameType::Configuration, packet);
let mut output = Vec::new();
while !encoder.is_exhausted() {
let mut buf = [0, 0];
let length = encoder.encode_chunk(&mut buf);
output.extend_from_slice(&buf[..length]);
}
assert_eq!(
output,
[
Constants::END,
Constants::CONFIGURATION,
0x40,
0x01,
0x00,
0x00,
0xbc,
0x38,
Constants::END
]
);
}
#[test]
fn chunked_2() {
chunked::<2>();
}
#[test]
fn chunked_3() {
chunked::<3>();
}
#[test]
#[cfg(feature = "std")]
fn direct_std() {
const DATA: &str = "Hello World!";
let input = Slipmux::Diagnostic(DATA.to_owned());
let result = encode_buffered(input);
assert_eq!(result, *b"\xc0\x0aHello World!\xc0");
let packet: &[u8] = &Packet::new().to_bytes().unwrap();
let input = Slipmux::Configuration(packet.to_vec());
let result = encode_buffered(input);
assert_eq!(
result,
[
Constants::END,
Constants::CONFIGURATION,
0x40,
0x01,
0x00,
0x00,
0xbc,
0x38,
Constants::END
]
);
}
#[test]
#[cfg(feature = "std")]
fn encode_buffered_max_encoding_size() {
let mut data: Vec<u8> = vec![
Constants::END,
Constants::ESC,
Constants::ESC,
Constants::END,
Constants::ESC,
Constants::END,
Constants::ESC,
Constants::END,
];
for _ in 0..=45 {
data.push(Constants::ESC);
}
assert_eq!(data.len(), 54);
let input = Slipmux::Configuration(data);
let result = encode_buffered(input);
assert_eq!(result.len(), 1 + 1 + 54 * 2 + 2 * 2 + 1);
let mut expected: Vec<u8> = vec![
Constants::END,
Constants::CONFIGURATION,
Constants::ESC,
Constants::ESC_END,
Constants::ESC,
Constants::ESC_ESC,
Constants::ESC,
Constants::ESC_ESC,
Constants::ESC,
Constants::ESC_END,
Constants::ESC,
Constants::ESC_ESC,
Constants::ESC,
Constants::ESC_END,
Constants::ESC,
Constants::ESC_ESC,
Constants::ESC,
Constants::ESC_END,
];
for _ in 0..=45 {
expected.push(Constants::ESC);
expected.push(Constants::ESC_ESC);
}
expected.push(Constants::ESC);
expected.push(Constants::ESC_END);
expected.push(Constants::ESC);
expected.push(Constants::ESC_END);
expected.push(Constants::END);
assert_eq!(result, expected);
}
}