use postcard::ser_flavors::{Cobs, Flavor, Slice};
use serde::Serialize;
use serde::de::DeserializeOwned;
use super::RynkError;
use super::command::Cmd;
pub const RYNK_HEADER_SIZE: usize = 3;
#[derive(Debug, Clone, Copy)]
pub struct RynkHeader {
pub cmd: Cmd,
pub seq: u8,
}
impl RynkHeader {
pub const fn parse(bytes: &[u8; RYNK_HEADER_SIZE]) -> Self {
Self {
cmd: Cmd::from_le_bytes([bytes[0], bytes[1]]),
seq: bytes[2],
}
}
pub fn peek(encoded: &[u8]) -> Option<Self> {
let mut out = [0u8; RYNK_HEADER_SIZE];
let mut n = 0;
let mut state = cobs::DecoderState::Idle;
for &byte in encoded {
match state.feed(byte).ok()? {
cobs::DecodeResult::NoData => {}
cobs::DecodeResult::DataComplete => return None,
cobs::DecodeResult::DataContinue(b) => {
out[n] = b;
n += 1;
if n == RYNK_HEADER_SIZE {
return Some(Self::parse(&out));
}
}
}
}
None
}
pub const fn to_bytes(&self) -> [u8; RYNK_HEADER_SIZE] {
let cmd_bytes = self.cmd.to_le_bytes();
[cmd_bytes[0], cmd_bytes[1], self.seq]
}
}
pub const fn max_wire_size(frame_size: usize) -> usize {
frame_size + frame_size / 254 + 2
}
pub(crate) const fn max_frame_size(max_encoded_size: usize) -> usize {
let mut len = 0;
while max_wire_size(len + 1) <= max_encoded_size {
len += 1;
}
len
}
pub const RYNK_MAX_PAYLOAD_SIZE: usize = {
let frame_size = max_frame_size(crate::constants::RYNK_BUFFER_SIZE);
assert!(
frame_size >= RYNK_HEADER_SIZE,
"rynk_buffer_size is too small for a COBS-framed header; increase it"
);
frame_size - RYNK_HEADER_SIZE
};
pub fn encode_frame<T: Serialize>(buf: &mut [u8], header: RynkHeader, value: &T) -> Result<usize, RynkError> {
let mut ser = postcard::Serializer {
output: Cobs::try_new(Slice::new(buf)).map_err(|_| RynkError::Internal)?,
};
ser.output
.try_extend(&header.to_bytes())
.map_err(|_| RynkError::Internal)?;
value.serialize(&mut ser).map_err(|_| RynkError::Internal)?;
Ok(ser.output.finalize().map_err(|_| RynkError::Internal)?.len())
}
struct BulkItems<I>(I);
impl<I> Serialize for BulkItems<I>
where
I: ExactSizeIterator + Clone,
I::Item: Serialize,
{
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeSeq;
let mut seq = ser.serialize_seq(Some(self.0.len()))?;
for item in self.0.clone() {
seq.serialize_element(&item)?;
}
seq.end()
}
}
pub struct RynkMessage<'a> {
buf: &'a mut [u8],
header: RynkHeader,
len: usize,
}
impl<'a> RynkMessage<'a> {
pub fn from_decoded(buf: &'a mut [u8], len: usize) -> Self {
debug_assert!(len >= RYNK_HEADER_SIZE && len <= buf.len());
let header = RynkHeader::parse(buf.first_chunk().unwrap());
Self { buf, header, len }
}
pub const fn header(&self) -> RynkHeader {
self.header
}
pub fn capacity(&self) -> usize {
self.buf.len()
}
pub fn frame(&self) -> &[u8] {
&self.buf[..self.len]
}
pub fn payload(&self) -> &[u8] {
&self.buf[RYNK_HEADER_SIZE..self.len]
}
pub fn decode_request<T: DeserializeOwned>(&self) -> Result<T, RynkError> {
postcard::from_bytes(self.payload()).map_err(|_| RynkError::Malformed)
}
pub fn encode_response<T: Serialize>(&mut self, value: &T) -> Result<(), RynkError> {
self.len = encode_frame(self.buf, self.header, &Ok::<&T, RynkError>(value))?;
Ok(())
}
pub fn encode_bulk<I>(&mut self, items: I) -> Result<(), RynkError>
where
I: ExactSizeIterator + Clone,
I::Item: Serialize,
{
self.len = encode_frame(self.buf, self.header, &Ok::<_, RynkError>(BulkItems(items)))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encoded_frame_round_trips_through_cobs() {
let mut buf = [0u8; 64];
let framed_len = encode_frame(
&mut buf,
RynkHeader {
cmd: Cmd::GetVersion,
seq: 0x42,
},
&[1u8, 2, 3, 4],
)
.unwrap();
assert_eq!(buf[framed_len - 1], 0);
assert!(buf[..framed_len - 1].iter().all(|&b| b != 0));
let n = cobs::decode_in_place(&mut buf[..framed_len - 1]).unwrap();
let header = RynkHeader::parse(buf[..RYNK_HEADER_SIZE].try_into().unwrap());
assert_eq!(header.cmd, Cmd::GetVersion);
assert_eq!(header.seq, 0x42);
assert_eq!(&buf[RYNK_HEADER_SIZE..n], &[1, 2, 3, 4]);
}
#[test]
fn encode_rejects_short_buffer() {
let mut buf = [0u8; 2];
assert_eq!(
encode_frame(
&mut buf,
RynkHeader {
cmd: Cmd::GetVersion,
seq: 0
},
&()
)
.err(),
Some(RynkError::Internal),
);
}
#[test]
fn encode_bulk_streams_the_ok_sequence_shape() {
let header = RynkHeader {
cmd: Cmd::GetKeymapBulk,
seq: 7,
};
let mut buf = [0u8; 64];
buf[..RYNK_HEADER_SIZE].copy_from_slice(&header.to_bytes());
let mut msg = RynkMessage::from_decoded(&mut buf, RYNK_HEADER_SIZE);
msg.encode_bulk([1u8, 2, 3].into_iter().map(|b| b * 2)).unwrap();
let mut expected = [0u8; 64];
let n = encode_frame(&mut expected, header, &Ok::<&[u8], RynkError>(&[2, 4, 6])).unwrap();
assert_eq!(msg.frame(), &expected[..n]);
}
#[test]
fn decoded_payload_spans_header_to_len() {
let mut buf = [0u8; 8];
buf[0..2].copy_from_slice(&Cmd::SetDefaultLayer.to_le_bytes());
buf[2] = 0x34;
buf[3..].copy_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE]);
let msg = RynkMessage::from_decoded(&mut buf, 8);
assert_eq!(msg.header().cmd, Cmd::SetDefaultLayer);
assert_eq!(msg.header().seq, 0x34);
assert_eq!(msg.payload(), &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE]);
}
#[test]
fn max_frame_size_is_exact_for_streaming_cobs() {
let nonzero = [0x41u8; 600];
let encodes_into = |logical: usize, physical: usize| {
let mut store = [0u8; 1024];
let mut ser = postcard::Serializer {
output: Cobs::try_new(Slice::new(&mut store[..physical])).unwrap(),
};
ser.output.try_extend(&nonzero[..logical]).is_ok() && ser.output.finalize().is_ok()
};
for physical in [
2usize, 3, 4, 255, 256, 257, 258, 259, 480, 488, 509, 510, 511, 512, 513, 514,
] {
let len = max_frame_size(physical);
assert!(
encodes_into(len, physical),
"max_frame_size({physical}) = {len} must fit"
);
assert!(
!encodes_into(len + 1, physical),
"{} must not fit in {physical}",
len + 1
);
}
assert_eq!(max_frame_size(0), 0);
assert_eq!(max_frame_size(1), 0);
assert_eq!(
RYNK_MAX_PAYLOAD_SIZE,
max_frame_size(crate::constants::RYNK_BUFFER_SIZE) - RYNK_HEADER_SIZE
);
}
#[test]
fn decode_header_reads_the_prefix_without_touching_the_frame() {
for (cmd, seq, payload) in [
(Cmd::GetVersion, 0x42u8, &[1u8, 2, 3, 4][..]), (Cmd::from_raw(0x0901), 0x00, &[][..]), (Cmd::from_raw(0x7FFF), 0xFF, &[0u8, 0][..]), ] {
let mut buf = [0u8; 64];
let n = encode_frame(&mut buf, RynkHeader { cmd, seq }, &payload).unwrap();
let copy = buf;
let header = RynkHeader::peek(&buf[..n]).expect("header decodes");
assert_eq!(header.cmd, cmd);
assert_eq!(header.seq, seq);
assert_eq!(buf, copy, "input frame must not be modified");
let header = RynkHeader::peek(&buf[..n - 1]).unwrap();
assert_eq!(header.cmd, cmd);
}
}
#[test]
fn decode_header_survives_a_254_byte_first_group() {
let payload = [0x41u8; 300];
let mut buf = [0u8; 400];
let header = RynkHeader {
cmd: Cmd::from_raw(0x0101),
seq: 7,
};
let n = encode_frame(&mut buf, header, &payload.as_slice()).unwrap();
let decoded = RynkHeader::peek(&buf[..n]).unwrap();
assert_eq!(decoded.cmd.raw(), 0x0101);
assert_eq!(decoded.seq, 7);
}
#[test]
fn decode_header_rejects_truncated_or_empty_input() {
assert!(RynkHeader::peek(&[]).is_none());
assert!(RynkHeader::peek(&[0x00]).is_none());
assert!(RynkHeader::peek(&[0x02, 0xAA]).is_none());
assert!(
RynkHeader::peek(&[0x03, 0xAA, 0xBB]).is_none(),
"only 2 decoded bytes + EOF"
);
}
#[test]
fn frame_never_contains_the_delimiter() {
let mut buf = [0u8; 64];
let n = encode_frame(
&mut buf,
RynkHeader {
cmd: Cmd::from_raw(0x0004),
seq: 0,
},
&[0u8, 0, 0],
)
.unwrap();
assert_eq!(buf[n - 1], 0, "frame is delimiter-terminated");
assert!(buf[..n - 1].iter().all(|&b| b != 0), "no interior 0x00");
}
}