cobs_io_async/sync.rs
1//! Synchronous COBS encoding and decoding between in-memory slices.
2//!
3//! This module is always available, including in `no_std` builds. It needs
4//! no executor or heap allocation. [`Reliable`](crate::Reliable) uses the
5//! same framing rules internally, but accepts only delimiter-terminated
6//! frames.
7//!
8//! | Operation | API |
9//! |---|---|
10//! | Encode a complete payload as an undelimited body | [`encode_from_slice`] |
11//! | Encode a payload with surrounding zero delimiters | [`encode_from_slice_including_sentinels`] |
12//! | Decode one frame from the start of a slice | [`decode_to_slice`] |
13//!
14//! Size encoding buffers with [`max_encoding_length`](crate::max_encoding_length),
15//! adding two bytes for delimiters. Decoding requires capacity only for the
16//! decoded payload.
17//!
18//! # Example
19//!
20//! Decode consecutive frames from one buffer, using
21//! [`DecodedFrame::consumed`] to locate the next frame.
22//!
23//! ```
24//! use cobs_io_async::{max_encoding_length, sync};
25//!
26//! let mut stream = [0u8; 2 * (max_encoding_length(3) + 2)];
27//! let first = sync::encode_from_slice_including_sentinels(&[7, 0, 8], &mut stream).unwrap();
28//! let second =
29//! sync::encode_from_slice_including_sentinels(&[9], &mut stream[first..]).unwrap();
30//! let mut input = &stream[..first + second];
31//!
32//! let mut payload = [0u8; 3];
33//! let frame = sync::decode_to_slice(input, &mut payload).unwrap();
34//! assert_eq!(&payload[..frame.len], &[7, 0, 8]);
35//! input = &input[frame.consumed..];
36//!
37//! let frame = sync::decode_to_slice(input, &mut payload).unwrap();
38//! assert_eq!(&payload[..frame.len], &[9]);
39//! assert!(input[frame.consumed..].is_empty());
40//! ```
41
42use core::convert::Infallible;
43
44use crate::codec::decode::decode_slice;
45use crate::{DecodeError, SeekableError};
46
47/// Result of successfully decoding one frame with [`decode_to_slice`].
48#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50#[cfg_attr(feature = "defmt", derive(defmt::Format))]
51pub struct DecodedFrame {
52 /// Number of decoded payload bytes written to the start of the destination.
53 pub len: usize,
54
55 /// Number of encoded bytes processed from the start of the source.
56 ///
57 /// Includes leading zero padding and the completing delimiter, if any.
58 /// Input following the frame starts at `source[consumed..]`.
59 pub consumed: usize,
60}
61
62/// Encodes all of `source` as one complete, undelimited COBS body into `dest`.
63///
64/// Returns the encoded length; the body occupies `dest[..len]`. Payload zeros
65/// are encoded as data. Empty input produces `[1]`. A terminal full block of
66/// 254 nonzero payload bytes does not receive a redundant trailing code byte.
67///
68/// A destination of [`max_encoding_length(source.len())`](crate::max_encoding_length)
69/// bytes is always sufficient.
70///
71/// # Errors
72///
73/// Returns [`SeekableError::OutOfBounds`] if `dest` is too small. The
74/// destination may then have been partially overwritten.
75///
76/// # Example
77///
78/// ```
79/// use cobs_io_async::sync::encode_from_slice;
80///
81/// let mut encoded = [0u8; 4];
82/// let len = encode_from_slice(&[7, 0, 8], &mut encoded).unwrap();
83/// assert_eq!(&encoded[..len], &[2, 7, 2, 8]);
84/// ```
85pub fn encode_from_slice(source: &[u8], dest: &mut [u8]) -> Result<usize, SeekableError> {
86 let mut output = SliceOutput { dest, len: 0 };
87 let mut code_idx = 0;
88 let mut code = 1u8;
89 let mut needs_code_placeholder = false;
90 output.append(0)?;
91 for &byte in source {
92 if needs_code_placeholder {
93 output.append(0)?;
94 needs_code_placeholder = false;
95 }
96 if byte == 0 {
97 output.set(code_idx, code)?;
98 code_idx = output.len;
99 output.append(0)?;
100 code = 1;
101 continue;
102 }
103 code += 1;
104 if code == u8::MAX {
105 output.set(code_idx, code)?;
106 output.append(byte)?;
107 code_idx = output.len;
108 code = 1;
109 needs_code_placeholder = true;
110 } else {
111 output.append(byte)?;
112 }
113 }
114 if !needs_code_placeholder {
115 output.set(code_idx, code)?;
116 }
117 Ok(output.len)
118}
119
120/// Encodes `source` with one leading and one trailing zero delimiter.
121///
122/// On success, `dest[..len]` contains `[0, encoded_body..., 0]`. Empty input
123/// produces `[0, 1, 0]`. Payload zeros remain data and do not split the frame.
124///
125/// Reserve [`max_encoding_length(source.len())`](crate::max_encoding_length)
126/// plus two bytes.
127///
128/// # Errors
129///
130/// Returns [`SeekableError::OutOfBounds`] if `dest` is too small. The
131/// destination may then have been partially overwritten.
132pub fn encode_from_slice_including_sentinels(
133 source: &[u8],
134 dest: &mut [u8],
135) -> Result<usize, SeekableError> {
136 let (first, rest) = dest.split_first_mut().ok_or(SeekableError::OutOfBounds)?;
137 *first = 0;
138 let body_len = encode_from_slice(source, rest)?;
139 *rest.get_mut(body_len).ok_or(SeekableError::OutOfBounds)? = 0;
140 Ok(body_len + 2)
141}
142
143/// Decodes the first COBS frame in `source` into the beginning of `dest`.
144///
145/// Leading zeros are ignored as padding. Decoding stops after the zero
146/// delimiter completing the frame; bytes after it are not examined, and
147/// [`DecodedFrame::consumed`] identifies where they begin.
148///
149/// If `source` ends before a delimiter, the frame is accepted when one has
150/// started and its current COBS block is structurally complete. Both `[1]`
151/// and `[1, 0]` decode to an empty payload. This end-of-input acceptance
152/// cannot detect truncation at a COBS block boundary; check that `consumed`
153/// includes a delimiter (`source[consumed - 1] == 0`) when the protocol
154/// requires delimited frames.
155///
156/// On success, bytes of `dest` after the decoded payload remain unchanged.
157///
158/// # Errors
159///
160/// - `DecodeError::Destination(SeekableError::OutOfBounds)`: `dest` cannot
161/// hold the decoded payload.
162/// - [`DecodeError::InvalidFrame`]: a delimiter arrived inside an incomplete
163/// block. The attached progress counts the invalidating delimiter, so the
164/// next frame starts at `source[progress.consumed as usize..]`.
165/// - [`DecodeError::UnexpectedSourceEof`]: `source` ended inside an
166/// incomplete block.
167/// - [`DecodeError::EmptyFrame`]: `source` is empty or contains only padding.
168///
169/// Errors may leave `dest` partially overwritten. Source errors are never
170/// returned.
171///
172/// # Example
173///
174/// ```
175/// use cobs_io_async::sync::decode_to_slice;
176///
177/// let source = [0, 2, 7, 2, 8, 0, 2, 9, 0];
178/// let mut output = [0x80u8; 5];
179/// let frame = decode_to_slice(&source, &mut output).unwrap();
180///
181/// assert_eq!(frame.len, 3);
182/// assert_eq!(output, [7, 0, 8, 0x80, 0x80]);
183/// assert_eq!(&source[frame.consumed..], &[2, 9, 0]);
184/// ```
185pub fn decode_to_slice(
186 source: &[u8],
187 dest: &mut [u8],
188) -> Result<DecodedFrame, DecodeError<Infallible, SeekableError>> {
189 decode_slice(source, dest)
190}
191
192struct SliceOutput<'a> {
193 dest: &'a mut [u8],
194 len: usize,
195}
196
197impl SliceOutput<'_> {
198 #[inline]
199 fn append(&mut self, byte: u8) -> Result<(), SeekableError> {
200 *self
201 .dest
202 .get_mut(self.len)
203 .ok_or(SeekableError::OutOfBounds)? = byte;
204 self.len += 1;
205 Ok(())
206 }
207
208 #[inline]
209 fn set(&mut self, idx: usize, byte: u8) -> Result<(), SeekableError> {
210 *self.dest.get_mut(idx).ok_or(SeekableError::OutOfBounds)? = byte;
211 Ok(())
212 }
213}