#![cfg_attr(not(any(feature = "std", test)), no_std)]
#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
#![warn(unreachable_pub)]
#![allow(unsafe_op_in_unsafe_fn)]
extern crate alloc;
mod error;
mod fastscan;
mod pattern;
mod pe;
mod scan;
pub use crate::error::{ParseErrorKind, ParsePatternError};
pub use crate::pattern::{Pattern, WildcardPattern};
pub use crate::scan::{
count_in_exec_sections, count_in_slice, count_in_text, find_in_exec_sections, find_in_slice,
find_in_text,
};
#[macro_export]
macro_rules! pattern {
[ $( $tok:tt ),* $(,)? ] => {
&[ $( $crate::__pattern_token!($tok) ),* ]
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __pattern_token {
(_) => {
::core::option::Option::<u8>::None
};
($byte:literal) => {
::core::option::Option::<u8>::Some($byte)
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pattern_macro_no_wildcards() {
const SIG: &[Option<u8>] = pattern![0x48, 0x8B, 0x05];
assert_eq!(SIG, &[Some(0x48), Some(0x8B), Some(0x05)]);
}
#[test]
fn pattern_macro_with_wildcards() {
const SIG: &[Option<u8>] = pattern![0x48, _, 0x05, _, _];
assert_eq!(SIG, &[Some(0x48), None, Some(0x05), None, None]);
}
#[test]
fn pattern_macro_trailing_comma() {
const SIG: &[Option<u8>] = pattern![0x48, 0x8B,];
assert_eq!(SIG, &[Some(0x48), Some(0x8B)]);
}
#[test]
fn pattern_macro_empty() {
const SIG: &[Option<u8>] = pattern![];
assert!(SIG.is_empty());
}
#[test]
fn pattern_macro_single_byte() {
const SIG: &[Option<u8>] = pattern![0xCC];
assert_eq!(SIG, &[Some(0xCC)]);
}
#[test]
fn pattern_macro_single_wildcard() {
const SIG: &[Option<u8>] = pattern![_];
assert_eq!(SIG, &[None]);
}
#[test]
fn error_display_empty() {
let e = ParsePatternError {
token_index: 0,
kind: ParseErrorKind::Empty,
};
let s = alloc::format!("{e}");
assert!(s.contains("no tokens"), "got: {s}");
}
#[test]
fn error_display_invalid_length() {
let e = ParsePatternError {
token_index: 3,
kind: ParseErrorKind::InvalidLength,
};
let s = alloc::format!("{e}");
assert!(s.contains("token #3"), "got: {s}");
assert!(s.contains("two hex digits"), "got: {s}");
}
#[test]
fn error_display_invalid_hex_digit() {
let e = ParsePatternError {
token_index: 1,
kind: ParseErrorKind::InvalidHexDigit,
};
let s = alloc::format!("{e}");
assert!(s.contains("token #1"), "got: {s}");
assert!(s.contains("non-hex"), "got: {s}");
}
#[test]
fn error_is_copy_and_clone() {
let e = ParsePatternError {
token_index: 0,
kind: ParseErrorKind::Empty,
};
let copied = e;
let cloned = e.clone();
assert_eq!(copied, e);
assert_eq!(cloned, e);
}
#[test]
fn error_kind_equality() {
assert_eq!(ParseErrorKind::Empty, ParseErrorKind::Empty);
assert_eq!(ParseErrorKind::InvalidLength, ParseErrorKind::InvalidLength);
assert_eq!(
ParseErrorKind::InvalidHexDigit,
ParseErrorKind::InvalidHexDigit
);
assert_ne!(ParseErrorKind::Empty, ParseErrorKind::InvalidLength);
assert_ne!(ParseErrorKind::Empty, ParseErrorKind::InvalidHexDigit);
assert_ne!(
ParseErrorKind::InvalidLength,
ParseErrorKind::InvalidHexDigit
);
}
#[cfg(feature = "std")]
#[test]
fn error_implements_std_error() {
fn assert_error<E: std::error::Error>(_: &E) {}
let e = ParsePatternError {
token_index: 0,
kind: ParseErrorKind::Empty,
};
assert_error(&e);
}
#[test]
fn pattern_clone_and_eq() {
let p1 = Pattern::from_ida("48 8B ?? 89").unwrap();
let p2 = p1.clone();
assert_eq!(p1, p2);
}
#[test]
fn pattern_debug() {
let p = Pattern::from_ida("48").unwrap();
let s = alloc::format!("{p:?}");
assert!(s.contains("Pattern"), "got: {s}");
}
#[test]
fn pattern_as_slice_round_trip() {
let p = Pattern::from_ida("48 ??").unwrap();
let s = p.as_slice();
assert_eq!(s.len(), 2);
assert_eq!(s[0], Some(0x48));
assert_eq!(s[1], None);
}
}