From 6b05c9cfcc607f5e82db2ff0b443839c31bd6e43 Mon Sep 17 00:00:00 2001 From: Sam Gwilym Date: Tue, 25 Jun 2024 08:50:07 +0100 Subject: [PATCH 1/2] Add successor fn to SubspaceId trait --- data-model/src/entry.rs | 6 +++++- data-model/src/grouping/area.rs | 6 +++++- data-model/src/grouping/range_3d.rs | 6 +++++- data-model/src/parameters.rs | 6 +++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/data-model/src/entry.rs b/data-model/src/entry.rs index b7cfaab..64bce32 100644 --- a/data-model/src/entry.rs +++ b/data-model/src/entry.rs @@ -112,7 +112,11 @@ mod tests { #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone)] struct FakeSubspaceId(usize); - impl SubspaceId for FakeSubspaceId {} + impl SubspaceId for FakeSubspaceId { + fn successor(&self) -> Option { + Some(FakeSubspaceId(self.0 + 1)) + } + } #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone)] struct FakePayloadDigest(usize); diff --git a/data-model/src/grouping/area.rs b/data-model/src/grouping/area.rs index 66c2289..1df5bf0 100644 --- a/data-model/src/grouping/area.rs +++ b/data-model/src/grouping/area.rs @@ -112,7 +112,11 @@ mod tests { #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] struct FakeSubspaceId(usize); - impl SubspaceId for FakeSubspaceId {} + impl SubspaceId for FakeSubspaceId { + fn successor(&self) -> Option { + Some(FakeSubspaceId(self.0 + 1)) + } + } #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone)] struct FakePayloadDigest(usize); diff --git a/data-model/src/grouping/range_3d.rs b/data-model/src/grouping/range_3d.rs index d0b9d81..4f70932 100644 --- a/data-model/src/grouping/range_3d.rs +++ b/data-model/src/grouping/range_3d.rs @@ -73,7 +73,11 @@ mod tests { #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] struct FakeSubspaceId(usize); - impl SubspaceId for FakeSubspaceId {} + impl SubspaceId for FakeSubspaceId { + fn successor(&self) -> Option { + Some(FakeSubspaceId(self.0 + 1)) + } + } #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone)] struct FakePayloadDigest(usize); diff --git a/data-model/src/parameters.rs b/data-model/src/parameters.rs index 97ff6aa..676892d 100644 --- a/data-model/src/parameters.rs +++ b/data-model/src/parameters.rs @@ -11,7 +11,11 @@ pub trait NamespaceId: Eq + Default + Clone {} /// ## Implementation notes /// /// The [`Default`] implementation **must** return the least element in the total order of [`SubspaceId`]. -pub trait SubspaceId: Ord + Default + Clone {} +pub trait SubspaceId: Ord + Default + Clone { + /// Return the next possible value in the set of all [`SubspaceId`]. + /// e.g. the successor of 3 is 4. + fn successor(&self) -> Option; +} /// A totally ordered type for [content-addressing](https://en.wikipedia.org/wiki/Content_addressing) the data that Willow stores. /// [Definition](https://willowprotocol.org/specs/data-model/index.html#PayloadDigest). From d2ba08d36e2813c11902c1e8b5c3892fc34bf013 Mon Sep 17 00:00:00 2001 From: Sam Gwilym Date: Thu, 27 Jun 2024 12:18:24 +0100 Subject: [PATCH 2/2] Add successor + prefix successor fns for paths + fuzz testing --- .gitignore | 2 +- Cargo.lock | 108 +++++++- Cargo.toml | 3 +- data-model/Cargo.lock | 116 +++++++++ data-model/Cargo.toml | 2 +- data-model/src/grouping/area.rs | 2 +- data-model/src/path.rs | 241 +++++++++++++++++- fuzz/.gitignore | 4 + fuzz/Cargo.toml | 56 ++++ fuzz/fuzz_targets/path_successor.rs | 18 ++ fuzz/fuzz_targets/path_successor_even_more.rs | 19 ++ fuzz/fuzz_targets/path_successor_more.rs | 18 ++ fuzz/fuzz_targets/successor_of_prefix.rs | 27 ++ .../successor_of_prefix_even_more.rs | 34 +++ fuzz/fuzz_targets/successor_of_prefix_more.rs | 27 ++ fuzz/src/lib.rs | 94 +++++++ 16 files changed, 758 insertions(+), 13 deletions(-) create mode 100644 data-model/Cargo.lock create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/path_successor.rs create mode 100644 fuzz/fuzz_targets/path_successor_even_more.rs create mode 100644 fuzz/fuzz_targets/path_successor_more.rs create mode 100644 fuzz/fuzz_targets/successor_of_prefix.rs create mode 100644 fuzz/fuzz_targets/successor_of_prefix_even_more.rs create mode 100644 fuzz/fuzz_targets/successor_of_prefix_more.rs create mode 100644 fuzz/src/lib.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..f2a4093 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/target +**/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3cfa19e..6982458 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,14 +3,114 @@ version = 3 [[package]] -name = "bytes" -version = "1.6.0" +name = "arbitrary" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "cc" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "willow-data-model" version = "0.1.0" dependencies = [ - "bytes", + "arbitrary", +] + +[[package]] +name = "willow-data-model-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "willow-data-model", ] diff --git a/Cargo.toml b/Cargo.toml index ec14140..9260ded 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] -members = ["data-model"] +members = ["data-model", "fuzz"] resolver = "2" + diff --git a/data-model/Cargo.lock b/data-model/Cargo.lock new file mode 100644 index 0000000..6982458 --- /dev/null +++ b/data-model/Cargo.lock @@ -0,0 +1,116 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "cc" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "willow-data-model" +version = "0.1.0" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "willow-data-model-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "willow-data-model", +] diff --git a/data-model/Cargo.toml b/data-model/Cargo.toml index 9e04d28..c2f09ac 100644 --- a/data-model/Cargo.toml +++ b/data-model/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -bytes = "1.4" +arbitrary = { version = "1.0.2", features = ["derive"]} \ No newline at end of file diff --git a/data-model/src/grouping/area.rs b/data-model/src/grouping/area.rs index 1df5bf0..44f8e80 100644 --- a/data-model/src/grouping/area.rs +++ b/data-model/src/grouping/area.rs @@ -4,7 +4,7 @@ use crate::{ path::Path, }; -use super::{range::Range, range_3d::Range3d}; +use super::range::Range; /// The possible values of an [`Area`]'s `subspace`. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] diff --git a/data-model/src/path.rs b/data-model/src/path.rs index a58fc10..657cb4c 100644 --- a/data-model/src/path.rs +++ b/data-model/src/path.rs @@ -1,3 +1,4 @@ +use arbitrary::{Arbitrary, Error as ArbitraryError, Unstructured}; use std::rc::Rc; #[derive(Debug)] @@ -17,6 +18,22 @@ pub trait PathComponent: Eq + AsRef<[u8]> + Clone + PartialOrd + Ord { /// Construct a new [`PathComponent`] from the provided slice, or return a [`ComponentTooLongError`] if the resulting component would be longer than [`PathComponent::MAX_COMPONENT_LENGTH`]. fn new(components: &[u8]) -> Result; + /// Construct a new [`PathComponent`] from the concatenation of `head` and the `tail`, or return a [`ComponentTooLongError`] if the resulting component would be longer than [`PathComponent::MAX_COMPONENT_LENGTH`]. + /// + /// This operation occurs when computing prefix successors, and the default implementation needs to perform an allocation. Implementers of this trait can override this with something more efficient if possible. + fn new_with_tail(head: &[u8], tail: u8) -> Result { + let mut vec = Vec::with_capacity(head.len() + 1); + vec.extend_from_slice(head); + vec.push(tail); + + Self::new(&vec) + } + + /// Return a new [`PathComponent`] which corresponds to the empty string. + fn empty() -> Self { + Self::new(&[]).unwrap() + } + /// The length of the component's `as_ref` bytes. fn len(&self) -> usize { self.as_ref().len() @@ -26,6 +43,58 @@ pub trait PathComponent: Eq + AsRef<[u8]> + Clone + PartialOrd + Ord { fn is_empty(&self) -> bool { self.len() == 0 } + + /// Try to append a zero byte to the end of the component. + /// Return `None` if the resulting component would be too long. + fn try_append_zero_byte(&self) -> Option { + if self.len() == Self::MAX_COMPONENT_LENGTH { + return None; + } + + let mut new_component_vec = Vec::with_capacity(self.len() + 1); + + new_component_vec.extend_from_slice(self.as_ref()); + new_component_vec.push(0); + + Some(Self::new(&new_component_vec).unwrap()) + } + + /// Create a new copy which differs at index `i`. + /// Implementers may panic if `i` is out of bound. + fn set_byte(&self, i: usize, value: u8) -> Self; + + /// Interpret the component as a binary number, and increment that number by 1. + /// If doing so would increase the bytelength of the component, return `None`. + fn try_increment_fixed_width(&self) -> Option { + // Wish we could avoid this allocation somehow. + let mut new_component = self.clone(); + + for i in (0..self.len()).rev() { + let byte = self.as_ref()[i]; + + if byte == 255 { + new_component = new_component.set_byte(i, 0); + } else { + return Some(new_component.set_byte(i, byte + 1)); + } + } + + None + } + + /// Return the least component which is greater than `self` but which is not prefixed by `self`. + fn prefix_successor(&self) -> Option { + for i in (0..self.len()).rev() { + if self.as_ref()[i] != 255 { + // Since we are not adjusting the length of the component this will always succeed. + return Some( + Self::new_with_tail(&self.as_ref()[0..i], self.as_ref()[i] + 1).unwrap(), + ); + } + } + + None + } } #[derive(Debug)] @@ -57,7 +126,19 @@ pub trait Path: PartialEq + Eq + PartialOrd + Ord + Clone { fn append(&self, component: Self::Component) -> Result; /// Return an iterator of all this path's components. - fn components(&self) -> impl Iterator; + /// + /// The `.len` method of the returned [`ExactSizeIterator`] must coincide with [`Self::component_count`]. + fn components( + &self, + ) -> impl DoubleEndedIterator + ExactSizeIterator; + + /// Return the number of [`PathComponent`] in the path. + fn component_count(&self) -> usize; + + /// Return whether the path has any [`PathComponent`] or not. + fn is_empty(&self) -> bool { + self.component_count() == 0 + } /// Create a new [`Path`] by taking the first `length` components of this path. fn create_prefix(&self, length: usize) -> Self; @@ -78,7 +159,7 @@ pub trait Path: PartialEq + Eq + PartialOrd + Ord + Clone { } } - true + self.component_count() <= other.component_count() } /// Test whether this path is prefixed by the given path. @@ -101,6 +182,53 @@ pub trait Path: PartialEq + Eq + PartialOrd + Ord + Clone { self.create_prefix(lcp_len) } + + /// Return the least path which is greater than `self`, or return `None` if `self` is the greatest possible path. + fn successor(&self) -> Option { + if self.component_count() == 0 { + let new_component = Self::Component::new(&[]).ok()?; + return Self::new(&[new_component]).ok(); + } + + // Try and add an empty component. + if let Ok(path) = self.append(Self::Component::empty()) { + return Some(path); + } + + for (i, component) in self.components().enumerate().rev() { + // Try and do the *next* simplest thing (add a 0 byte to the component). + if let Some(component) = component.try_append_zero_byte() { + if let Ok(path) = self.create_prefix(i).append(component) { + return Some(path); + } + } + + // Otherwise we need to increment the component fixed-width style! + if let Some(incremented_component) = component.try_increment_fixed_width() { + // We can unwrap here because neither the max path length, component count, or component length has changed. + return Some(self.create_prefix(i).append(incremented_component).unwrap()); + } + } + + None + } + + /// Return the least path that is greater than `self` and which is not prefixed by `self`, or `None` if `self` is the empty path *or* if `self` is the greatest path. + fn successor_of_prefix(&self) -> Option { + for (i, component) in self.components().enumerate().rev() { + if let Some(successor_comp) = component.try_append_zero_byte() { + if let Ok(path) = self.create_prefix(i).append(successor_comp) { + return Some(path); + } + } + + if let Some(successor_comp) = component.prefix_successor() { + return self.create_prefix(i).append(successor_comp).ok(); + } + } + + None + } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -130,6 +258,14 @@ impl PathComponent for PathComponentBox { fn is_empty(&self) -> bool { self.0.is_empty() } + + fn set_byte(&self, i: usize, value: u8) -> Self { + let mut new_component = self.clone(); + + new_component.0[i] = value; + + new_component + } } impl AsRef<[u8]> for PathComponentBox { @@ -138,7 +274,19 @@ impl AsRef<[u8]> for PathComponentBox { } } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +impl<'a, const MCL: usize> Arbitrary<'a> for PathComponentBox { + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + let boxx: Box<[u8]> = Arbitrary::arbitrary(u)?; + Self::new(&boxx).map_err(|_| ArbitraryError::IncorrectFormat) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + as Arbitrary<'a>>::size_hint(depth) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] /// A cheaply cloneable [`Path`] using a `Rc<[PathComponentBox]>`. /// While cloning is cheap, operations which return modified forms of the path (e.g. [`Path::append`]) are not, as they have to clone and adjust the contents of the underlying [`Rc`]. pub struct PathRc( @@ -211,13 +359,57 @@ impl Path for PathRc impl Iterator { - self.0.iter() + fn components( + &self, + ) -> impl DoubleEndedIterator + ExactSizeIterator + { + return self.0.iter(); + } + + fn component_count(&self) -> usize { + self.0.len() + } +} + +impl Ord for PathRc { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + for (my_comp, your_comp) in self.components().zip(other.components()) { + let comparison = my_comp.cmp(your_comp); + + match comparison { + std::cmp::Ordering::Equal => { /* Continue */ } + _ => return comparison, + } + } + + self.component_count().cmp(&other.component_count()) + } +} + +impl PartialOrd for PathRc { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl<'a, const MCL: usize, const MCC: usize, const MPL: usize> Arbitrary<'a> + for PathRc +{ + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + let boxx: Box<[PathComponentBox]> = Arbitrary::arbitrary(u)?; + Self::new(&boxx).map_err(|_| ArbitraryError::IncorrectFormat) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + as Arbitrary<'a>>::size_hint(depth) } } #[cfg(test)] mod tests { + use std::cmp::Ordering::Less; + use super::*; const MCL: usize = 8; @@ -406,6 +598,15 @@ mod tests { assert!(path_a.is_prefix_of(&path_b)); assert!(!path_a.is_prefix_of(&path_c)); + + let path_d = PathRc::::new(&[PathComponentBox::new(&[]).unwrap()]).unwrap(); + let path_e = PathRc::::new(&[PathComponentBox::new(&[0]).unwrap()]).unwrap(); + + assert!(!path_d.is_prefix_of(&path_e)); + + let empty_path = PathRc::empty(); + + assert!(empty_path.is_prefix_of(&path_d)); } #[test] @@ -485,4 +686,34 @@ mod tests { PathRc::::new(&[PathComponentBox::new(&[b'a']).unwrap()]).unwrap() ) } + + fn make_test_path( + vector: &Vec>, + ) -> PathRc { + let components: Vec<_> = vector + .iter() + .map(|bytes_vec| PathComponentBox::new(bytes_vec).expect("the component was too long")) + .collect(); + + PathRc::new(&components).expect("the path was invalid") + } + + #[test] + fn ordering() { + let test_vector = vec![(vec![vec![0, 0], vec![]], vec![vec![0, 0, 0]], Less)]; + + for (a, b, expected) in test_vector { + let path_a: PathRc<3, 3, 3> = make_test_path(&a); + let path_b: PathRc<3, 3, 3> = make_test_path(&b); + + let ordering = path_a.cmp(&path_b); + + if ordering != expected { + println!("a: {:?}", a); + println!("b: {:?}", b); + + assert_eq!(ordering, expected); + } + } + } } diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..1a45eee --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..1bf7ea0 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "willow-data-model-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.willow-data-model] +path = "../data-model" + +[[bin]] +name = "path_successor" +path = "fuzz_targets/path_successor.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "path_successor_more" +path = "fuzz_targets/path_successor_more.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "path_successor_even_more" +path = "fuzz_targets/path_successor_even_more.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "successor_of_prefix" +path = "fuzz_targets/successor_of_prefix.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "successor_of_prefix_more" +path = "fuzz_targets/successor_of_prefix_more.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "successor_of_prefix_even_more" +path = "fuzz_targets/successor_of_prefix_even_more.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/path_successor.rs b/fuzz/fuzz_targets/path_successor.rs new file mode 100644 index 0000000..88e4cf7 --- /dev/null +++ b/fuzz/fuzz_targets/path_successor.rs @@ -0,0 +1,18 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use willow_data_model::path::*; +use willow_data_model_fuzz::test_successor; + +// MCL, MCC, MPL +fuzz_target!(|data: (PathRc<3, 3, 3>, PathRc<3, 3, 3>)| { + let (baseline, candidate) = data; + let max_path = PathRc::new(&[ + PathComponentBox::new(&[255, 255]).unwrap(), + PathComponentBox::new(&[255]).unwrap(), + PathComponentBox::new(&[]).unwrap(), + ]) + .unwrap(); + + test_successor(baseline, candidate, max_path); +}); diff --git a/fuzz/fuzz_targets/path_successor_even_more.rs b/fuzz/fuzz_targets/path_successor_even_more.rs new file mode 100644 index 0000000..0217af3 --- /dev/null +++ b/fuzz/fuzz_targets/path_successor_even_more.rs @@ -0,0 +1,19 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use willow_data_model::path::*; +use willow_data_model_fuzz::test_successor; + +// MCL, MCC, MPL +fuzz_target!(|data: (PathRc<4, 4, 16>, PathRc<4, 4, 16>)| { + let (baseline, candidate) = data; + let max_path = PathRc::new(&[ + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + ]) + .unwrap(); + + test_successor(baseline, candidate, max_path); +}); diff --git a/fuzz/fuzz_targets/path_successor_more.rs b/fuzz/fuzz_targets/path_successor_more.rs new file mode 100644 index 0000000..3caaf71 --- /dev/null +++ b/fuzz/fuzz_targets/path_successor_more.rs @@ -0,0 +1,18 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use willow_data_model::path::*; +use willow_data_model_fuzz::test_successor; + +// MCL, MCC, MPL +fuzz_target!(|data: (PathRc<3, 3, 3>, PathRc<3, 3, 3>)| { + let (baseline, candidate) = data; + let max_path = PathRc::new(&[ + PathComponentBox::new(&[255, 255, 255]).unwrap(), + PathComponentBox::new(&[]).unwrap(), + PathComponentBox::new(&[]).unwrap(), + ]) + .unwrap(); + + test_successor(baseline, candidate, max_path); +}); diff --git a/fuzz/fuzz_targets/successor_of_prefix.rs b/fuzz/fuzz_targets/successor_of_prefix.rs new file mode 100644 index 0000000..21d60db --- /dev/null +++ b/fuzz/fuzz_targets/successor_of_prefix.rs @@ -0,0 +1,27 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use willow_data_model::path::*; +use willow_data_model_fuzz::test_successor_of_prefix; + +// MCL, MCC, MPL +fuzz_target!(|data: (PathRc<2, 3, 3>, PathRc<2, 3, 3>)| { + let (baseline, candidate) = data; + let unsucceedables = [ + PathRc::empty(), + PathRc::new(&[PathComponentBox::new(&[255, 255]).unwrap()]).unwrap(), + PathRc::new(&[ + PathComponentBox::new(&[255, 255]).unwrap(), + PathComponentBox::new(&[255]).unwrap(), + ]) + .unwrap(), + PathRc::new(&[ + PathComponentBox::new(&[255, 255]).unwrap(), + PathComponentBox::new(&[255]).unwrap(), + PathComponentBox::empty(), + ]) + .unwrap(), + ]; + + test_successor_of_prefix(baseline, candidate, &unsucceedables); +}); diff --git a/fuzz/fuzz_targets/successor_of_prefix_even_more.rs b/fuzz/fuzz_targets/successor_of_prefix_even_more.rs new file mode 100644 index 0000000..e042c81 --- /dev/null +++ b/fuzz/fuzz_targets/successor_of_prefix_even_more.rs @@ -0,0 +1,34 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use willow_data_model::path::*; +use willow_data_model_fuzz::test_successor_of_prefix; + +// MCL, MCC, MPL +fuzz_target!(|data: (PathRc<4, 4, 16>, PathRc<4, 4, 16>)| { + let (baseline, candidate) = data; + let unsucceedables = [ + PathRc::empty(), + PathRc::new(&[PathComponentBox::new(&[255, 255, 255, 255]).unwrap()]).unwrap(), + PathRc::new(&[ + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + ]) + .unwrap(), + PathRc::new(&[ + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + ]) + .unwrap(), + PathRc::new(&[ + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + PathComponentBox::new(&[255, 255, 255, 255]).unwrap(), + ]) + .unwrap(), + ]; + + test_successor_of_prefix(baseline, candidate, &unsucceedables); +}); diff --git a/fuzz/fuzz_targets/successor_of_prefix_more.rs b/fuzz/fuzz_targets/successor_of_prefix_more.rs new file mode 100644 index 0000000..1ad99d4 --- /dev/null +++ b/fuzz/fuzz_targets/successor_of_prefix_more.rs @@ -0,0 +1,27 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use willow_data_model::path::*; +use willow_data_model_fuzz::test_successor_of_prefix; + +// MCL, MCC, MPL +fuzz_target!(|data: (PathRc<3, 3, 3>, PathRc<3, 3, 3>)| { + let (baseline, candidate) = data; + let unsucceedables = [ + PathRc::empty(), + PathRc::new(&[PathComponentBox::new(&[255, 255, 255]).unwrap()]).unwrap(), + PathRc::new(&[ + PathComponentBox::new(&[255, 255, 255]).unwrap(), + PathComponentBox::empty(), + ]) + .unwrap(), + PathRc::new(&[ + PathComponentBox::new(&[255, 255, 255]).unwrap(), + PathComponentBox::empty(), + PathComponentBox::empty(), + ]) + .unwrap(), + ]; + + test_successor_of_prefix(baseline, candidate, &unsucceedables); +}); diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs new file mode 100644 index 0000000..94fb6bc --- /dev/null +++ b/fuzz/src/lib.rs @@ -0,0 +1,94 @@ +use willow_data_model::path::*; + +pub fn test_successor( + baseline: PathRc, + candidate: PathRc, + max_path: PathRc, +) { + let successor = baseline.successor(); + + match successor { + None => { + if baseline != max_path { + println!("\n\n\n"); + println!("baseline: {:?}", baseline); + println!("successor: {:?}", successor); + println!("candidate: {:?}", candidate); + println!("\n\n\n"); + panic!("returned None when the path was NOT the greatest path! BoooOOOoo") + } + } + Some(successor) => { + if successor <= baseline { + println!("\n\n\n"); + println!("baseline: {:?}", baseline); + println!("successor: {:?}", successor); + println!("candidate: {:?}", candidate); + println!("\n\n\n"); + + panic!("successor was not greater than the path it was derived from! BooooOoooOOo") + } + + if candidate < successor && candidate > baseline { + println!("\n\n\n"); + println!("baseline: {:?}", baseline); + println!("successor: {:?}", successor); + println!("candidate: {:?}", candidate); + println!("\n\n\n"); + + panic!("the successor generated was NOT the immediate successor! BooooOOOOo!") + } + } + } +} + +pub fn test_successor_of_prefix( + baseline: PathRc, + candidate: PathRc, + unsucceedable: &[PathRc], +) { + let prefix_successor = baseline.successor_of_prefix(); + + match prefix_successor { + None => { + if !unsucceedable.iter().any(|unsuc| unsuc == &baseline) { + println!("\n\n\n"); + println!("baseline: {:?}", baseline); + println!("successor: {:?}", prefix_successor); + println!("candidate: {:?}", candidate); + panic!("returned None when the path was NOT the greatest path! BoooOOOoo\n\n\n\n"); + } + } + Some(prefix_successor) => { + if prefix_successor <= baseline { + println!("\n\n\n"); + println!("baseline: {:?}", baseline); + println!("successor: {:?}", prefix_successor); + println!("candidate: {:?}", candidate); + panic!("the successor is meant to be greater than the baseline, but wasn't!! BOOOOOOOOO\n\n\n\n"); + } + + if prefix_successor.is_prefixed_by(&baseline) { + println!("\n\n\n"); + println!("baseline: {:?}", baseline); + println!("successor: {:?}", prefix_successor); + println!("candidate: {:?}", candidate); + panic!("successor was prefixed by the path it was derived from! BoooOOooOOooOo\n\n\n\n"); + } + + if !baseline.is_prefix_of(&candidate) + && candidate < prefix_successor + && candidate > baseline + { + println!("\n\n\n"); + println!("baseline: {:?}", baseline); + println!("successor: {:?}", prefix_successor); + println!("candidate: {:?}", candidate); + + panic!( + "the successor generated was NOT the immediate prefix successor! BooooOOOOo!\n\n\n\n" + ); + } + } + } +}