1#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
6pub enum EncodeError {
7 #[error("buffer too small: have {have} bytes, want {want} bytes")]
8 BufferTooSmall { have: usize, want: usize },
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
13pub enum DecodeError {
14 #[error("empty input")]
15 EmptyInput,
16 #[error("buffer too small: have {have} bytes, want {want} bytes")]
17 BufferTooSmall { have: usize, want: usize },
18 #[error("zero marker at position {at}")]
19 ZeroMarker { at: usize },
20 #[error("zero byte in data at position {at}")]
21 ZeroBinary { at: usize },
22 #[error("chunk overflow at position {at}: chunk {marker} exceeds data length {len}")]
23 ChunkOverflow { at: usize, marker: u8, len: usize },
24}
25
26#[inline]
28pub const fn encode_buffer(size: usize) -> usize {
29 size + size.div_ceil(254) + 1
30}
31
32#[inline]
34pub const fn decode_buffer(size: usize) -> usize {
35 if size == 0 {
36 return 0;
42 }
43 size - 1
44}
45
46#[inline]
50pub fn encode(data: &[u8], encoded: &mut [u8]) -> Result<usize, EncodeError> {
51 let want = encode_buffer(data.len());
52 if encoded.len() < want {
53 return Err(EncodeError::BufferTooSmall {
54 have: encoded.len(),
55 want,
56 });
57 }
58 Ok(encode_unsafe(data, encoded))
59}
60
61#[inline]
67pub fn encode_unsafe(data: &[u8], encoded: &mut [u8]) -> usize {
68 if data.is_empty() {
70 encoded[0] = 0x01;
71 return 1;
72 }
73 debug_assert!(encoded.len() >= encode_buffer(data.len()));
75
76 unsafe {
79 let mut input_pos = 0usize;
80 let mut output_pos = 0usize;
81
82 loop {
83 let run = if *data.get_unchecked(input_pos) == 0 {
85 Some(0)
86 } else {
87 memchr::memchr(0, data.get_unchecked(input_pos..))
88 };
89 let mut rem = run.unwrap_or(data.len() - input_pos);
91 while rem >= 254 {
92 *encoded.get_unchecked_mut(output_pos) = 0xff;
93 core::ptr::copy_nonoverlapping(
94 data.as_ptr().add(input_pos),
95 encoded.as_mut_ptr().add(output_pos + 1),
96 254,
97 );
98 input_pos += 254;
99 output_pos += 255;
100 rem -= 254;
101 }
102 if run.is_some() {
103 *encoded.get_unchecked_mut(output_pos) = rem as u8 + 1;
105 core::ptr::copy_nonoverlapping(
106 data.as_ptr().add(input_pos),
107 encoded.as_mut_ptr().add(output_pos + 1),
108 rem,
109 );
110 input_pos += rem + 1;
111 output_pos += rem + 1;
112
113 if input_pos == data.len() {
115 *encoded.get_unchecked_mut(output_pos) = 0x01;
116 return output_pos + 1;
117 }
118 } else {
119 if rem > 0 {
122 *encoded.get_unchecked_mut(output_pos) = rem as u8 + 1;
123 core::ptr::copy_nonoverlapping(
124 data.as_ptr().add(input_pos),
125 encoded.as_mut_ptr().add(output_pos + 1),
126 rem,
127 );
128 output_pos += rem + 1;
129 }
130 return output_pos;
131 }
132 }
133 }
134}
135
136#[inline]
140pub fn decode(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
141 if data.is_empty() {
142 return Err(DecodeError::EmptyInput);
143 }
144 if data.len() > 1 {
145 let want = decode_buffer(data.len());
146 if decoded.len() < want {
147 return Err(DecodeError::BufferTooSmall {
148 have: decoded.len(),
149 want,
150 });
151 }
152 }
153 decode_unsafe(data, decoded)
154}
155
156#[inline]
162pub fn decode_unsafe(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
163 if data.is_empty() {
165 return Err(DecodeError::EmptyInput);
166 }
167 if data.len() == 1 && data[0] == 0x01 {
169 return Ok(0);
170 }
171 debug_assert!(decoded.len() >= decode_buffer(data.len()));
173
174 if memchr::memchr(0, data).is_some() {
179 return decode_scalar(data, decoded);
180 }
181 decode_chunked::<false>(data, decoded)
182}
183
184#[inline]
192pub fn decode_nonzero(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
193 if data.is_empty() {
194 return Err(DecodeError::EmptyInput);
195 }
196 if data.len() > 1 {
197 let want = decode_buffer(data.len());
198 if decoded.len() < want {
199 return Err(DecodeError::BufferTooSmall {
200 have: decoded.len(),
201 want,
202 });
203 }
204 }
205 decode_nonzero_unsafe(data, decoded)
206}
207
208#[inline]
215pub fn decode_nonzero_unsafe(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
216 if data.is_empty() {
218 return Err(DecodeError::EmptyInput);
219 }
220 if data.len() == 1 && data[0] == 0x01 {
222 return Ok(0);
223 }
224 debug_assert!(decoded.len() >= decode_buffer(data.len()));
226
227 decode_chunked::<true>(data, decoded)
228}
229
230#[inline]
240fn decode_chunked<const CHECKED: bool>(
241 data: &[u8],
242 decoded: &mut [u8],
243) -> Result<usize, DecodeError> {
244 unsafe {
245 let mut input_pos = 0usize;
246 let mut output_pos = 0usize;
247
248 while input_pos + 255 < data.len() {
258 let marker = *data.get_unchecked(input_pos);
259 if CHECKED && marker == 0 {
260 return Err(DecodeError::ZeroMarker { at: input_pos });
261 }
262 let chunk = marker as usize - 1;
263 input_pos += 1;
264
265 core::ptr::copy_nonoverlapping(
266 data.as_ptr().add(input_pos),
267 decoded.as_mut_ptr().add(output_pos),
268 16,
269 );
270 if chunk > 16 {
271 core::ptr::copy_nonoverlapping(
272 data.as_ptr().add(input_pos),
273 decoded.as_mut_ptr().add(output_pos),
274 254,
275 );
276 }
277 input_pos += chunk;
278 output_pos += chunk;
279
280 *decoded.get_unchecked_mut(output_pos) = 0;
281 output_pos += (marker != 0xff) as usize;
282 }
283 loop {
285 let marker = *data.get_unchecked(input_pos);
287 if CHECKED && marker == 0 {
288 return Err(DecodeError::ZeroMarker { at: input_pos });
289 }
290 let chunk = marker as usize - 1;
291 input_pos += 1;
292
293 if input_pos + chunk > data.len() {
294 return Err(DecodeError::ChunkOverflow {
295 at: input_pos - 1,
296 marker,
297 len: data.len(),
298 });
299 }
300 core::ptr::copy_nonoverlapping(
302 data.as_ptr().add(input_pos),
303 decoded.as_mut_ptr().add(output_pos),
304 chunk,
305 );
306 input_pos += chunk;
307 output_pos += chunk;
308
309 if input_pos == data.len() {
311 return Ok(output_pos);
312 }
313 if marker != 0xff {
315 *decoded.get_unchecked_mut(output_pos) = 0;
316 output_pos += 1;
317 }
318 }
319 }
320}
321
322#[cold]
329#[inline(never)]
330fn decode_scalar(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
331 unsafe {
333 let mut output_pos = 0usize;
334 let mut i = 0usize;
335
336 while i < data.len() {
337 let marker = *data.get_unchecked(i);
339 if marker == 0 {
340 return Err(DecodeError::ZeroMarker { at: i });
341 }
342 i += 1;
343
344 if i + (marker as usize) - 1 > data.len() {
346 return Err(DecodeError::ChunkOverflow {
347 at: i - 1,
348 marker,
349 len: data.len(),
350 });
351 }
352 for _ in 1..marker {
354 let b = *data.get_unchecked(i);
355 if b == 0 {
356 return Err(DecodeError::ZeroBinary { at: i });
357 }
358 *decoded.get_unchecked_mut(output_pos) = b;
359 output_pos += 1;
360 i += 1;
361 }
362 if i < data.len() && marker != 0xff {
364 *decoded.get_unchecked_mut(output_pos) = 0;
365 output_pos += 1;
366 }
367 }
368 Ok(output_pos)
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn test_roundtrip_empty() {
378 let data = [];
379 let mut enc_buf = [0u8; 1];
380 let len = encode(&data, &mut enc_buf).unwrap();
381 assert_eq!(len, 1);
382 assert_eq!(enc_buf[0], 0x01);
383
384 let mut dec_buf = [0u8; 0];
385 let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
386 assert_eq!(dec_len, 0);
387 }
388
389 #[test]
390 fn test_roundtrip_no_zeros() {
391 let data = [1, 2, 3, 4, 5];
392 let mut enc_buf = [0u8; encode_buffer(5)];
393 let len = encode(&data, &mut enc_buf).unwrap();
394
395 let mut dec_buf = [0u8; decode_buffer(encode_buffer(5))];
396 let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
397 assert_eq!(&dec_buf[..dec_len], &data);
398 }
399
400 #[test]
401 fn test_roundtrip_with_zeros() {
402 let data = [0, 1, 0, 2, 0, 0, 3];
403 let mut enc_buf = [0u8; encode_buffer(7)];
404 let len = encode(&data, &mut enc_buf).unwrap();
405
406 let mut dec_buf = [0u8; decode_buffer(encode_buffer(7))];
407 let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
408 assert_eq!(&dec_buf[..dec_len], &data);
409 }
410
411 #[test]
412 fn test_roundtrip_254_nonzero() {
413 let data: Vec<u8> = (1..=254).collect();
414 let mut enc_buf = vec![0u8; encode_buffer(254)];
415 let len = encode(&data, &mut enc_buf).unwrap();
416
417 let mut dec_buf = vec![0u8; decode_buffer(enc_buf.len())];
418 let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
419 assert_eq!(&dec_buf[..dec_len], &data[..]);
420 }
421
422 #[test]
423 fn test_roundtrip_255_nonzero() {
424 let data: Vec<u8> = (1..=254).chain(std::iter::once(1)).collect();
425 let mut enc_buf = vec![0u8; encode_buffer(255)];
426 let len = encode(&data, &mut enc_buf).unwrap();
427
428 let mut dec_buf = vec![0u8; decode_buffer(enc_buf.len())];
429 let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
430 assert_eq!(&dec_buf[..dec_len], &data[..]);
431 }
432
433 #[test]
434 fn test_roundtrip_chunk_boundaries() {
435 let sizes: Vec<usize> = if cfg!(miri) {
436 (0..=64)
437 .chain([253, 254, 255, 256, 507, 508, 509, 510, 1021, 1024])
438 .collect()
439 } else {
440 (0..=515)
441 .chain([1021, 1024, 4093, 4096, 8191, 65536])
442 .collect()
443 };
444 for size in sizes {
445 for period in [1usize, 2, 3, 253, 254, 255, 256] {
446 for phase in [0, period - 1] {
447 let data: Vec<u8> = (0..size)
448 .map(|i| {
449 if i % period == phase {
450 0
451 } else {
452 (i % 251 + 1) as u8
453 }
454 })
455 .collect();
456 roundtrip_reference(&data);
457 }
458 }
459 let data: Vec<u8> = (0..size).map(|i| (i % 251 + 1) as u8).collect();
460 roundtrip_reference(&data);
461 }
462 }
463
464 fn roundtrip_reference(data: &[u8]) {
467 let mut encoded = vec![0u8; encode_buffer(data.len())];
468 let encoded_len = encode(data, &mut encoded).unwrap();
469
470 let mut reference = vec![0u8; cobs::max_encoding_length(data.len())];
471 let reference_len = cobs::encode(data, &mut reference);
472 assert_eq!(&encoded[..encoded_len], &reference[..reference_len]);
473
474 let mut decoded = vec![0u8; decode_buffer(encoded_len)];
475 let decoded_len = decode(&encoded[..encoded_len], &mut decoded).unwrap();
476 assert_eq!(&decoded[..decoded_len], data);
477
478 let mut nonzero = vec![0u8; decode_buffer(encoded_len)];
479 let nonzero_len = decode_nonzero(&encoded[..encoded_len], &mut nonzero).unwrap();
480 assert_eq!(&nonzero[..nonzero_len], data);
481 }
482
483 #[test]
484 fn test_decode_malformed() {
485 let mut buffer = [0u8; 16];
486
487 assert_eq!(decode(&[], &mut buffer), Err(DecodeError::EmptyInput));
488 assert_eq!(
489 decode(&[0x00], &mut buffer),
490 Err(DecodeError::ZeroMarker { at: 0 })
491 );
492 assert_eq!(
493 decode(&[0x02, 0x41, 0x00], &mut buffer),
494 Err(DecodeError::ZeroMarker { at: 2 })
495 );
496 assert_eq!(
497 decode(&[0x02, 0x00], &mut buffer),
498 Err(DecodeError::ZeroBinary { at: 1 })
499 );
500 assert_eq!(
501 decode(&[0x03, 0x41, 0x00, 0x41], &mut buffer),
502 Err(DecodeError::ZeroBinary { at: 2 })
503 );
504 assert_eq!(
505 decode(&[0x03, 0x41], &mut buffer),
506 Err(DecodeError::ChunkOverflow {
507 at: 0,
508 marker: 3,
509 len: 2
510 })
511 );
512 assert_eq!(
513 decode(&[0x05, 0x41, 0x00, 0x41], &mut buffer),
514 Err(DecodeError::ChunkOverflow {
515 at: 0,
516 marker: 5,
517 len: 4
518 })
519 );
520 }
521
522 #[test]
523 fn test_decode_nonzero_malformed() {
524 let mut buffer = [0u8; 128];
525
526 assert_eq!(
527 decode_nonzero(&[], &mut buffer),
528 Err(DecodeError::EmptyInput)
529 );
530 assert_eq!(
531 decode_nonzero(&[0x03, 0x41], &mut buffer),
532 Err(DecodeError::ChunkOverflow {
533 at: 0,
534 marker: 3,
535 len: 2
536 })
537 );
538 let mut long = Vec::new();
541 for _ in 0..26 {
542 long.extend_from_slice(&[0x03, 0x41, 0x42]);
543 }
544 long.extend_from_slice(&[0x05, 0x41]);
545 assert_eq!(
546 decode_nonzero(&long, &mut buffer),
547 decode(&long, &mut [0u8; 128])
548 );
549 long[40] = 0;
552 let _ = decode_nonzero(&long, &mut buffer);
553 }
554
555 #[test]
556 fn test_buffer_too_small() {
557 let mut buffer = [0u8; 2];
558
559 assert_eq!(
560 encode(&[1, 2, 3], &mut buffer),
561 Err(EncodeError::BufferTooSmall {
562 have: 2,
563 want: encode_buffer(3)
564 })
565 );
566 assert_eq!(
567 decode(&[0x02, 0x41, 0x02, 0x42], &mut buffer),
568 Err(DecodeError::BufferTooSmall { have: 2, want: 3 })
569 );
570 }
571}