use crate::{
parsing,
pattern::{
DynamicPattern,
Method,
PatternRef,
StaticPattern,
},
prefilter::{
CompiledPrefilter,
PrefilterError,
},
Error,
RawPrefilter,
Sealed,
};
use chumsky::{
primitive::end,
Parser as _,
};
use std::ops::Range;
#[derive(Clone, Copy, Debug)]
pub struct Match<'haystack> {
range: (usize, usize),
haystack: &'haystack [u8],
}
impl<'haystack> Match<'haystack> {
#[must_use]
pub fn start(&self) -> usize {
self.range.0
}
#[must_use]
pub fn end(&self) -> usize {
self.range.1
}
#[must_use]
pub fn range(&self) -> Range<usize> {
self.start()..self.end()
}
#[must_use]
pub fn as_bytes(&self) -> &'haystack [u8] {
&self.haystack[self.range()]
}
}
#[expect(clippy::len_without_is_empty)]
pub trait Needle: Sealed {
#[must_use]
fn find<'haystack>(&self, haystack: &'haystack [u8]) -> Option<Match<'haystack>> {
self.find_iter(haystack).next()
}
#[must_use]
fn find_iter<'needle, 'haystack>(
&'needle self,
haystack: &'haystack [u8],
) -> Find<'needle, 'haystack>;
#[must_use]
fn len(&self) -> usize;
}
pub struct Find<'needle, 'haystack> {
prefilter: CompiledPrefilter,
pattern: PatternRef<'needle>,
haystack: &'haystack [u8],
last_offset: usize,
}
impl Find<'_, '_> {
#[must_use]
pub fn search_method(&self) -> Method {
self.pattern.method()
}
}
impl<'haystack> Iterator for Find<'_, 'haystack> {
type Item = Match<'haystack>;
fn next(&mut self) -> Option<Self::Item> {
macro_rules! failure {
() => {{
self.last_offset = self.haystack.len();
return None;
}};
}
macro_rules! success {
($start:ident, $end:ident) => {{
self.last_offset = $start + 1;
return Some(Match {
range: ($start, $end),
haystack: self.haystack,
});
}};
}
let mut prefilter_iter = self.prefilter.find_iter(&self.haystack[self.last_offset..]);
loop {
let prefilter_offset = match prefilter_iter.next() {
Some(Ok(offset)) => offset,
Some(Err(PrefilterError::HaystackTooSmall { offset })) => {
self.last_offset += offset;
break;
}
None => failure!(),
};
let start = self.last_offset + prefilter_offset;
let end = start + self.pattern.len();
let Some(haystack) = &self.haystack.get(start..end) else {
failure!();
};
if unsafe { self.pattern.cmpeq_unchecked(haystack) } {
success!(start, end);
}
}
for (window_offset, window) in self.haystack[self.last_offset..]
.windows(self.pattern.len())
.enumerate()
{
if unsafe { self.pattern.cmpeq_unchecked(window) } {
let start = self.last_offset + window_offset;
let end = start + self.pattern.len();
success!(start, end);
}
}
failure!();
}
}
#[derive(Clone, Debug)]
pub struct StaticNeedle<const NEEDLE_LEN: usize, const BUFFER_LEN: usize> {
prefilter: RawPrefilter,
pattern: StaticPattern<NEEDLE_LEN, BUFFER_LEN>,
}
impl<const NEEDLE_LEN: usize, const BUFFER_LEN: usize> StaticNeedle<NEEDLE_LEN, BUFFER_LEN> {
#[doc(hidden)]
#[must_use]
pub const fn new(
prefilter: RawPrefilter,
word: [u8; BUFFER_LEN],
mask: [u8; BUFFER_LEN],
) -> Self {
Self {
prefilter,
pattern: StaticPattern::from_components(word, mask),
}
}
}
impl<const NEEDLE_LEN: usize, const BUFFER_LEN: usize> Sealed
for StaticNeedle<NEEDLE_LEN, BUFFER_LEN>
{
}
impl<const NEEDLE_LEN: usize, const BUFFER_LEN: usize> Needle
for StaticNeedle<NEEDLE_LEN, BUFFER_LEN>
{
fn find_iter<'needle, 'haystack>(
&'needle self,
haystack: &'haystack [u8],
) -> Find<'needle, 'haystack> {
let pattern: PatternRef<'_> = (&self.pattern).into();
let prefilter = match self.prefilter {
RawPrefilter::Length { len } => CompiledPrefilter::from_length(len),
RawPrefilter::Prefix {
prefix,
prefix_offset,
} => CompiledPrefilter::from_prefix(prefix, prefix_offset),
RawPrefilter::PrefixPostfix {
prefix: _,
prefix_offset,
postfix: _,
postfix_offset,
} => CompiledPrefilter::from_prefix_postfix(
pattern.word_slice(),
prefix_offset.into(),
postfix_offset.into(),
),
};
Find {
prefilter,
pattern,
haystack,
last_offset: 0,
}
}
fn len(&self) -> usize {
NEEDLE_LEN
}
}
#[derive(Clone, Debug)]
pub struct DynamicNeedle {
prefilter: CompiledPrefilter,
pattern: DynamicPattern,
}
impl DynamicNeedle {
pub fn from_ida(pattern: &str) -> Result<Self, Error<'_>> {
let parser = parsing::ida_pattern().then_ignore(end());
match parser.parse(pattern) {
Ok(ok) => Ok(Self::from_bytes(&ok)),
Err(mut errors) => {
let error = errors
.drain(..)
.next()
.expect("failure to parse should produce at least one error");
Err(Error {
source: pattern,
inner: error,
})
}
}
}
#[must_use]
pub fn from_bytes(bytes: &[Option<u8>]) -> Self {
let pattern = DynamicPattern::from_bytes(bytes);
Self {
prefilter: CompiledPrefilter::from_bytes((&pattern).into()),
pattern,
}
}
#[doc(hidden)]
#[must_use]
pub fn serialize_word(&self) -> &[u8] {
self.pattern.word_slice_padded()
}
#[doc(hidden)]
#[must_use]
pub fn serialize_mask(&self) -> &[u8] {
self.pattern.mask_slice_padded()
}
#[doc(hidden)]
#[must_use]
pub fn serialize_prefilter(&self) -> RawPrefilter {
(&self.prefilter).into()
}
#[cfg(test)]
#[must_use]
pub(crate) fn prefilter(&self) -> &CompiledPrefilter {
&self.prefilter
}
}
impl Sealed for DynamicNeedle {}
impl Needle for DynamicNeedle {
fn find_iter<'needle, 'haystack>(
&'needle self,
haystack: &'haystack [u8],
) -> Find<'needle, 'haystack> {
Find {
prefilter: self.prefilter.clone(),
pattern: (&self.pattern).into(),
haystack,
last_offset: 0,
}
}
fn len(&self) -> usize {
self.pattern.len()
}
}
#[cfg(test)]
mod tests {
use super::{
DynamicNeedle,
Needle as _,
};
#[test]
fn test_from_ida() {
assert!(DynamicNeedle::from_ida("4_ 42 41 43 41 42 41 42 43").is_err());
assert!(DynamicNeedle::from_ida("11 ??? 22").is_err());
macro_rules! test_success {
($pattern:literal, $length:literal) => {
let needle = DynamicNeedle::from_ida($pattern);
assert!(needle.is_ok(), "\"{}\"", $pattern);
let needle = needle.unwrap();
assert_eq!(needle.len(), $length, "\"{}\"", $pattern);
};
}
test_success!("41 42 41 43 41 42 41 42 43", 9);
test_success!("41 42 41 43 41 42 41 42 41", 9);
test_success!(
"50 41 52 54 49 43 49 50 41 54 45 20 49 4E 20 50 41 52 41 43 48 55 54 45",
24
);
test_success!("11 ? ? 22 ? 33 44 ?", 8);
test_success!("aA Bb 1d", 3);
test_success!("11 ? 33 ?? 55 ? ?? 88", 8);
}
}