Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 33 additions & 45 deletions framez/src/codec/delimiter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,68 +14,53 @@ use crate::{
/// This codec tracks progress using an internal state of the underlying buffer, and it must not be used across multiple framing sessions.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Delimiter<'a> {
pub struct Delimiter {
/// The delimiter to search for.
delimiter: &'a [u8],
delimiter: u8,
/// The number of bytes of the slice that have been seen so far.
seen: usize,
}

impl<'a> Delimiter<'a> {
impl Delimiter {
/// Creates a new [`Delimiter`] with the given `delimiter`.
#[inline]
pub const fn new(delimiter: &'a [u8]) -> Self {
pub const fn new(delimiter: u8) -> Self {
Self { delimiter, seen: 0 }
}

/// Returns the delimiter to search for.
#[inline]
pub const fn delimiter(&self) -> &'a [u8] {
pub const fn delimiter(&self) -> u8 {
self.delimiter
}
}

impl DecodeError for Delimiter<'_> {
impl DecodeError for Delimiter {
type Error = Infallible;
}

impl<'buf> Decoder<'buf> for Delimiter<'_> {
impl<'buf> Decoder<'buf> for Delimiter {
type Item = &'buf [u8];

fn decode(&mut self, src: &'buf mut [u8]) -> Result<Option<(Self::Item, usize)>, Self::Error> {
if src.len() < self.delimiter.len() {
if src.is_empty() {
return Ok(None);
}

match self.delimiter.last() {
None => {
let bytes = &src[..self.seen + 1];
while self.seen < src.len() {
if src[self.seen] == self.delimiter {
let bytes = &src[..self.seen];
let item = (bytes, self.seen + 1);

Ok(Some(item))
}
Some(last_byte) => {
while self.seen < src.len() {
if src[self.seen] == *last_byte {
let src_delimiter =
&src[self.seen + 1 - self.delimiter.len()..self.seen + 1];

if src_delimiter == self.delimiter {
let bytes = &src[..self.seen + 1 - self.delimiter.len()];
let item = (bytes, self.seen + 1);

self.seen = 0;

return Ok(Some(item));
}
}
self.seen = 0;

self.seen += 1;
}

Ok(None)
return Ok(Some(item));
}

self.seen += 1;
}

Ok(None)
}
}

Expand All @@ -97,18 +82,18 @@ impl core::fmt::Display for DelimiterEncodeError {

impl core::error::Error for DelimiterEncodeError {}

impl Encoder<&[u8]> for Delimiter<'_> {
impl Encoder<&[u8]> for Delimiter {
type Error = DelimiterEncodeError;

fn encode(&mut self, item: &[u8], dst: &mut [u8]) -> Result<usize, Self::Error> {
let size = item.len() + self.delimiter.len();
let size = item.len() + 1;

if dst.len() < size {
return Err(DelimiterEncodeError::BufferTooSmall);
}

dst[..item.len()].copy_from_slice(item);
dst[item.len()..size].copy_from_slice(self.delimiter);
dst[item.len()..size].copy_from_slice(&[self.delimiter]);

Ok(size)
}
Expand All @@ -134,17 +119,17 @@ mod test {

// cspell: disable
let items: &[&[u8]] = &[
b"jh asjd##ppppppppppppppp##",
b"k hb##jsjuwjal kadj##jsadhjiu##w",
b"##jal kadjjsadhjiuwqens ##",
b"jh asjd#ppppppppppppppp#",
b"k hb#jsjuwjal kadj#jsadhjiu#w",
b"#jal kadjjsadhjiuwqens #",
b"nd ",
b"yxxcjajsdi##askdn as",
b"jdasd##iouqw es",
b"sd##k",
b"yxxcjajsdi#askdn as",
b"jdasd#iouqw es",
b"sd#k",
];
// cspell: enable

let decoder = Delimiter::new(b"##");
let decoder = Delimiter::new(b'#');

let expected: &[&[u8]] = &[];
framed_read!(items, expected, decoder, 1, BufferTooSmall);
Expand All @@ -165,7 +150,7 @@ mod test {
// cspell: disable

let expected: &[&[u8]] = &[b"jh asjd"];
framed_read!(items, expected, decoder, 16, BufferTooSmall);
framed_read!(items, expected, decoder, 14, BufferTooSmall);

let expected: &[&[u8]] = &[
b"jh asjd",
Expand Down Expand Up @@ -200,10 +185,13 @@ mod test {
b"Hei".to_vec(),
b"sup".to_vec(),
b"Hey".to_vec(),
b"He".to_vec(),
b"H".to_vec(),
b"".to_vec(),
];

let decoder = Delimiter::new(b"###");
let encoder = Delimiter::new(b"###");
let decoder = Delimiter::new(b'#');
let encoder = Delimiter::new(b'#');
let map = |item: &[u8]| item.to_vec();

sink_stream!(encoder, decoder, items, map);
Expand Down
7 changes: 4 additions & 3 deletions fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ embedded-io-adapters = { version = "0.6.1", default-features = false, features =
] }
heapless = { version = "0.8.0", default-features = false }
framez = { path = "../framez", default-features = false }
arbitrary = "1.4.2"

[workspace]
members = ["."]
Expand Down
7 changes: 6 additions & 1 deletion fuzz/fuzz_targets/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#![no_main]

use arbitrary::Unstructured;
use framez::{
codec::{
bytes::Bytes,
Expand All @@ -20,7 +21,11 @@ fuzz_target!(|data: &[u8]| {
let buf = &mut [0_u8; 64];
let data = &mut std::vec::Vec::from(data);

let mut codec = Delimiter::new(b"#");
let delimiter = Unstructured::new(data)
.arbitrary::<u8>()
.expect("Failed to generate delimiter");

let mut codec = Delimiter::new(delimiter);
let _ = codec.decode(data).expect("Must be Infallible");

let mut codec = Bytes::new();
Expand Down
7 changes: 6 additions & 1 deletion fuzz/fuzz_targets/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#![no_main]

use arbitrary::Unstructured;
use framez::{
codec::{
bytes::Bytes,
Expand All @@ -19,7 +20,11 @@ use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let buf = &mut [0_u8; 64];

let mut codec = Delimiter::new(b"#");
let delimiter = Unstructured::new(data)
.arbitrary::<u8>()
.expect("Failed to generate delimiter");

let mut codec = Delimiter::new(delimiter);
let _ = codec.encode(data, buf);

let mut codec = Bytes::new();
Expand Down
52 changes: 31 additions & 21 deletions fuzz/fuzz_targets/send_receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::{
fmt::{Debug, Display},
};

use arbitrary::Unstructured;
use embedded_io_adapters::tokio_1::FromTokio;
use framez::{
codec::{
Expand All @@ -27,27 +28,36 @@ use libfuzzer_sys::fuzz_target;
use tokio::runtime::Runtime;

fuzz_target!(|data: &[u8]| {
Runtime::new().expect("Runtime must build").block_on(async {
fuzz(data, Delimiter::new(b"#"), Delimiter::new(b"#"), |data| {
(!data.contains(&b'#')).then_some(data).ok_or(())
})
.await
.unwrap();

fuzz(data, Lines::new(), Lines::new(), |data| {
(!data.contains(&b'\n')).then_some(data).ok_or(())
})
.await
.unwrap();

fuzz(data, StrLines::new(), StrLines::new(), |data| {
(!data.contains(&b'\n')).then_some(data).ok_or(())?;

str::from_utf8(data).map_err(|_| ())
})
.await
.unwrap();
});
Runtime::new()
.expect("Runtime must build")
.block_on(async move {
let delimiter = Unstructured::new(data)
.arbitrary::<u8>()
.expect("Failed to generate delimiter");

fuzz(
data,
Delimiter::new(delimiter),
Delimiter::new(delimiter),
|data| (!data.contains(&delimiter)).then_some(data).ok_or(()),
)
.await
.unwrap();

fuzz(data, Lines::new(), Lines::new(), |data| {
(!data.contains(&b'\n')).then_some(data).ok_or(())
})
.await
.unwrap();

fuzz(data, StrLines::new(), StrLines::new(), |data| {
(!data.contains(&b'\n')).then_some(data).ok_or(())?;

str::from_utf8(data).map_err(|_| ())
})
.await
.unwrap();
});
});

// Note: Bytes can not be fuzzed like this
Expand Down
Loading