Skip to content

Commit

Permalink
add more fuzzing
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes committed Oct 27, 2023
1 parent ee1a4a9 commit a8c074e
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 58 deletions.
14 changes: 9 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,21 @@ rustdoc-args = ["--cfg", "docsrs"]
cfg-if = "1"
hex = { version = "~0.4.2", optional = true, default-features = false }
serde = { version = "1.0", optional = true, default-features = false }
proptest = { version = "1.3.1", default-features = false, optional = true }

[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]
cpufeatures = "0.2"

[dev-dependencies]
hex = "~0.4.2"
hex = { version = "~0.4.2", default-features = false }
hex-literal = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }

[features]
default = ["std"]
std = ["hex?/std", "serde?/std", "alloc"]
alloc = ["hex?/alloc", "serde?/alloc"]
std = ["hex?/std", "serde?/std", "proptest?/std", "alloc"]
alloc = ["hex?/alloc", "serde?/alloc", "proptest?/alloc"]

# Serde support. Use with `#[serde(with = "const_hex")]`
serde = ["hex?/serde", "dep:serde"]
Expand All @@ -51,5 +52,8 @@ force-generic = []
# the specialized implementations.
portable-simd = []

# Nightly features for better performance.
# Enables nightly-only features for better performance.
nightly = []

# Internal features.
__fuzzing = ["dep:proptest", "std"]
2 changes: 1 addition & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ publish = false
cargo-fuzz = true

[dependencies]
const-hex = { path = ".." }
const-hex = { path = "..", features = ["__fuzzing"] }
libfuzzer-sys = "0.4"

[[bin]]
Expand Down
54 changes: 2 additions & 52 deletions fuzz/fuzz_targets/fuzz_const_hex.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,7 @@
#![no_main]

use libfuzzer_sys::fuzz_target;
use std::io::Write;

fn mk_expected(bytes: &[u8]) -> String {
let mut s = Vec::with_capacity(bytes.len() * 2);
for i in bytes {
write!(s, "{i:02x}").unwrap();
}
unsafe { String::from_utf8_unchecked(s) }
}

fn test_buffer<const N: usize, const LEN: usize>(bytes: &[u8]) {
if let Ok(bytes) = <[u8; N]>::try_from(bytes) {
let mut buffer = const_hex::Buffer::<N, false>::new();
let string = buffer.format(&bytes).to_string();
assert_eq!(string.len(), bytes.len() * 2);
assert_eq!(string.as_bytes(), buffer.as_byte_array::<LEN>());
assert_eq!(string, buffer.as_str());
assert_eq!(string, mk_expected(&bytes));

let mut buffer = const_hex::Buffer::<N, true>::new();
let prefixed = buffer.format(&bytes).to_string();
assert_eq!(prefixed.len(), 2 + bytes.len() * 2);
assert_eq!(prefixed, buffer.as_str());
assert_eq!(prefixed, format!("0x{string}"));
}
}

fuzz_target!(|input: &[u8]| {
fuzz_encode(input);
fuzz_decode(input);
fuzz_target!(|data: &[u8]| {
const_hex::fuzzing::fuzz(data).unwrap();
});

fn fuzz_encode(input: &[u8]) {
test_buffer::<8, 16>(input);
test_buffer::<20, 40>(input);
test_buffer::<32, 64>(input);
test_buffer::<64, 128>(input);
test_buffer::<128, 256>(input);

let encoded = const_hex::encode(input);
let expected = mk_expected(input);
assert_eq!(encoded, expected);

let decoded = const_hex::decode(&encoded).unwrap();
assert_eq!(decoded, input);
}

fn fuzz_decode(input: &[u8]) {
if let Ok(decoded) = const_hex::decode(input) {
let prefix = if input.starts_with(b"0x") { 2 } else { 0 };
let input_len = (input.len() - prefix) / 2;
assert_eq!(decoded.len(), input_len);
}
}
94 changes: 94 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,3 +634,97 @@ unsafe fn invalid_hex_error(input: &[u8]) -> FromHexError {
index,
}
}

#[allow(missing_docs, unused)]
#[cfg(feature = "__fuzzing")]
pub mod fuzzing {
use proptest::test_runner::TestCaseResult;
use proptest::{prop_assert, prop_assert_eq};
use std::io::Write;

pub fn fuzz(data: &[u8]) -> TestCaseResult {
self::encode(&data)?;
self::decode(&data)?;
Ok(())
}

pub fn encode(input: &[u8]) -> TestCaseResult {
test_buffer::<8, 16>(input)?;
test_buffer::<20, 40>(input)?;
test_buffer::<32, 64>(input)?;
test_buffer::<64, 128>(input)?;
test_buffer::<128, 256>(input)?;

let encoded = crate::encode(input);
let expected = mk_expected(input);
prop_assert_eq!(&encoded, &expected);

let decoded = crate::decode(&encoded).unwrap();
prop_assert_eq!(decoded, input);

Ok(())
}

pub fn decode(input: &[u8]) -> TestCaseResult {
if let Ok(decoded) = crate::decode(input) {
let prefix = if input.starts_with(b"0x") { 2 } else { 0 };
let input_len = (input.len() - prefix) / 2;
prop_assert_eq!(decoded.len(), input_len);
}

Ok(())
}

fn mk_expected(bytes: &[u8]) -> String {
let mut s = Vec::with_capacity(bytes.len() * 2);
for i in bytes {
write!(s, "{i:02x}").unwrap();
}
unsafe { String::from_utf8_unchecked(s) }
}

fn test_buffer<const N: usize, const LEN: usize>(bytes: &[u8]) -> TestCaseResult {
if let Ok(bytes) = <&[u8; N]>::try_from(bytes) {
let mut buffer = crate::Buffer::<N, false>::new();
let string = buffer.format(bytes).to_string();
prop_assert_eq!(string.len(), bytes.len() * 2);
prop_assert_eq!(string.as_bytes(), buffer.as_byte_array::<LEN>());
prop_assert_eq!(string.as_str(), buffer.as_str());
prop_assert_eq!(string.as_str(), mk_expected(bytes));

let mut buffer = crate::Buffer::<N, true>::new();
let prefixed = buffer.format(bytes).to_string();
prop_assert_eq!(prefixed.len(), 2 + bytes.len() * 2);
prop_assert_eq!(prefixed.as_str(), buffer.as_str());
prop_assert_eq!(prefixed, format!("0x{string}"));
}

Ok(())
}

proptest::proptest! {
#![proptest_config(proptest::prelude::ProptestConfig {
cases: 1024,
..Default::default()
})]

#[test]
fn fuzz_encode(s in ".+") {
encode(s.as_bytes())?;
}

#[test]
fn fuzz_check_true(s in "[0-9a-fA-F]+") {
prop_assert!(crate::check_raw(&s));
if s.len() % 2 == 0 {
prop_assert!(crate::check(&s).is_ok());
}
}

#[test]
fn fuzz_check_false(ref s in ".{16}[^0-9a-fA-F]+") {
prop_assert!(crate::check(&s).is_err());
prop_assert!(!crate::check_raw(&s));
}
}
}

0 comments on commit a8c074e

Please sign in to comment.