pub fn find_all<'a>(
bytes: &'a [u8],
pattern: &'a [Option<u8>],
) -> impl Iterator<Item = usize> + 'a {
let bytes = if pattern.is_empty() { &[][..] } else { bytes };
bytes
.windows(pattern.len().max(1))
.enumerate()
.filter_map(move |(offset, candidate)| {
pattern
.iter()
.zip(candidate)
.all(|(expected, actual)| expected.is_none_or(|expected| expected == *actual))
.then_some(offset)
})
}
#[cfg(test)]
mod tests {
use super::find_all;
#[test]
fn a_partial_match_does_not_hide_a_later_match() {
let pattern = [Some(0x48), Some(0x8b), Some(0x0d)];
assert_eq!(
find_all(&[0x48, 0x48, 0x8b, 0x0d], &pattern).collect::<Vec<_>>(),
[1]
);
}
#[test]
fn empty_or_short_inputs_have_no_matches() {
assert!(find_all(&[], &[]).next().is_none());
assert!(find_all(&[0x48], &[]).next().is_none());
assert!(find_all(&[], &[None]).next().is_none());
assert!(
find_all(&[0x48], &[Some(0x48), Some(0x8b)])
.next()
.is_none()
);
}
#[test]
fn matches_at_zero_and_the_last_possible_offset() {
let pattern = [Some(0x48), Some(0x8b), Some(0x0d)];
assert_eq!(
find_all(&[0x48, 0x8b, 0x0d], &pattern).collect::<Vec<_>>(),
[0]
);
assert_eq!(
find_all(&[0x48, 0x8b, 0x0d, 0x90, 0x48, 0x8b, 0x0d], &pattern).collect::<Vec<_>>(),
[0, 4]
);
}
#[test]
fn overlapping_matches_are_all_returned() {
assert_eq!(
find_all(b"AAA", &[Some(b'A'), Some(b'A')]).collect::<Vec<_>>(),
[0, 1]
);
assert_eq!(
find_all(b"AAAB", &[Some(b'A'), Some(b'A'), Some(b'B')]).collect::<Vec<_>>(),
[1]
);
}
#[test]
fn wildcards_each_consume_one_byte() {
assert_eq!(
find_all(&[0xff, 0, 0xaa, 0xbb, 0, 0xcc], &[None, Some(0), None]).collect::<Vec<_>>(),
[0, 3]
);
assert_eq!(
find_all(&[0, 1, 2], &[None, None]).collect::<Vec<_>>(),
[0, 1]
);
assert!(find_all(&[0], &[Some(0), None]).next().is_none());
}
#[test]
fn zero_and_ff_are_literal_bytes() {
assert_eq!(
find_all(&[0xff, 0, 0xff], &[Some(0), Some(0xff)]).collect::<Vec<_>>(),
[1]
);
assert!(
find_all(&[1, 0xff], &[Some(0), Some(0xff)])
.next()
.is_none()
);
}
}