initial commit
This commit is contained in:
commit
a2922b8bad
59 changed files with 2684583 additions and 0 deletions
87
src/sig.rs
Normal file
87
src/sig.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
//! Byte-pattern signatures: parse "55 48 89 ? E5" and scan a byte haystack for matches.
|
||||
|
||||
/// A signature pattern; `None` entries are wildcards (`?` / `??`).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pattern {
|
||||
bytes: Vec<Option<u8>>,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
pub fn parse(s: &str) -> anyhow::Result<Self> {
|
||||
let mut bytes = Vec::new();
|
||||
for tok in s.split_whitespace() {
|
||||
match tok {
|
||||
"?" | "??" | "*" => bytes.push(None),
|
||||
hex => {
|
||||
let b = u8::from_str_radix(hex, 16)
|
||||
.map_err(|_| anyhow::anyhow!("bad signature token {tok:?}"))?;
|
||||
bytes.push(Some(b));
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow::ensure!(!bytes.is_empty(), "empty signature");
|
||||
anyhow::ensure!(bytes.iter().any(Option::is_some), "all-wildcard signature");
|
||||
Ok(Self { bytes })
|
||||
}
|
||||
|
||||
/// First concrete (non-wildcard) byte and its index — used as a cheap scan prefilter.
|
||||
fn anchor(&self) -> (usize, u8) {
|
||||
self.bytes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, b)| b.map(|v| (i, v)))
|
||||
.expect("parse() guarantees at least one concrete byte")
|
||||
}
|
||||
|
||||
/// Byte offsets in `hay` where this pattern matches.
|
||||
pub fn find_all(&self, hay: &[u8]) -> Vec<usize> {
|
||||
let n = self.bytes.len();
|
||||
let mut out = Vec::new();
|
||||
if hay.len() < n {
|
||||
return out;
|
||||
}
|
||||
let (ai, av) = self.anchor();
|
||||
// SIMD-scan for the anchor byte (memchr), full-match only at candidate starts.
|
||||
// A match starting at `pos` puts its anchor at `pos + ai`, so valid anchor indices
|
||||
// are [ai, hay.len()-n+ai]; scanning [..=hi] finds every match's anchor, none missed.
|
||||
let hi = hay.len() - n + ai;
|
||||
for apos in memchr::memchr_iter(av, &hay[..=hi]) {
|
||||
if apos < ai {
|
||||
continue;
|
||||
}
|
||||
let pos = apos - ai;
|
||||
if self
|
||||
.bytes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.all(|(i, b)| b.is_none_or(|v| hay[pos + i] == v))
|
||||
{
|
||||
out.push(pos);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_and_match() {
|
||||
let p = Pattern::parse("55 48 ? E5").unwrap();
|
||||
let hay = [0x00, 0x55, 0x48, 0x99, 0xE5, 0x55, 0x48, 0x11, 0xE5];
|
||||
assert_eq!(p.find_all(&hay), vec![1, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_question_is_wildcard() {
|
||||
let p = Pattern::parse("90 ?? 90").unwrap();
|
||||
assert_eq!(p.find_all(&[0x90, 0xAB, 0x90]), vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_wildcard_rejected() {
|
||||
assert!(Pattern::parse("? ??").is_err());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue