use alloc::vec::Vec;
use crate::error::{ParseErrorKind, ParsePatternError};
pub type WildcardPattern<'a> = &'a [Option<u8>];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pattern {
bytes: Vec<Option<u8>>,
}
impl Pattern {
#[must_use]
pub fn new(bytes: Vec<Option<u8>>) -> Self {
Self { bytes }
}
pub fn from_ida(s: &str) -> Result<Self, ParsePatternError> {
let mut bytes = Vec::new();
for (idx, tok) in s.split_ascii_whitespace().enumerate() {
let entry = match tok {
"?" | "??" => None,
_ => Some(parse_hex_byte(tok).map_err(|kind| ParsePatternError {
token_index: idx,
kind,
})?),
};
bytes.push(entry);
}
if bytes.is_empty() {
return Err(ParsePatternError {
token_index: 0,
kind: ParseErrorKind::Empty,
});
}
Ok(Self { bytes })
}
#[must_use]
pub fn as_slice(&self) -> WildcardPattern<'_> {
&self.bytes
}
#[must_use]
pub fn len(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
}
fn parse_hex_byte(tok: &str) -> Result<u8, ParseErrorKind> {
let bytes = tok.as_bytes();
if bytes.len() != 2 {
return Err(ParseErrorKind::InvalidLength);
}
let hi = hex_digit(bytes[0]).ok_or(ParseErrorKind::InvalidHexDigit)?;
let lo = hex_digit(bytes[1]).ok_or(ParseErrorKind::InvalidHexDigit)?;
Ok((hi << 4) | lo)
}
fn hex_digit(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_basic() {
let p = Pattern::from_ida("48 8B 05").unwrap();
assert_eq!(p.as_slice(), &[Some(0x48), Some(0x8B), Some(0x05)]);
}
#[test]
fn parse_with_wildcards() {
let p = Pattern::from_ida("48 ?? 05 ??").unwrap();
assert_eq!(p.as_slice(), &[Some(0x48), None, Some(0x05), None]);
}
#[test]
fn parse_single_question_wildcard() {
let p = Pattern::from_ida("48 ? 05").unwrap();
assert_eq!(p.as_slice(), &[Some(0x48), None, Some(0x05)]);
}
#[test]
fn parse_case_insensitive() {
let upper = Pattern::from_ida("AB CD EF").unwrap();
let lower = Pattern::from_ida("ab cd ef").unwrap();
let mixed = Pattern::from_ida("aB Cd eF").unwrap();
assert_eq!(upper.as_slice(), lower.as_slice());
assert_eq!(lower.as_slice(), mixed.as_slice());
}
#[test]
fn parse_extra_whitespace_ok() {
let p = Pattern::from_ida(" 48\t8B\n??\r\n05 ").unwrap();
assert_eq!(p.as_slice(), &[Some(0x48), Some(0x8B), None, Some(0x05)]);
}
#[test]
fn parse_empty_errors() {
let err = Pattern::from_ida("").unwrap_err();
assert_eq!(err.kind, ParseErrorKind::Empty);
}
#[test]
fn parse_invalid_hex_errors() {
let err = Pattern::from_ida("48 ZZ 05").unwrap_err();
assert_eq!(err.token_index, 1);
assert_eq!(err.kind, ParseErrorKind::InvalidHexDigit);
}
#[test]
fn parse_invalid_low_nibble_only() {
let err = Pattern::from_ida("48 1Z 05").unwrap_err();
assert_eq!(err.token_index, 1);
assert_eq!(err.kind, ParseErrorKind::InvalidHexDigit);
}
#[test]
fn parse_invalid_length_errors() {
let err = Pattern::from_ida("48 8 05").unwrap_err();
assert_eq!(err.token_index, 1);
assert_eq!(err.kind, ParseErrorKind::InvalidLength);
}
#[test]
fn pattern_new_from_vec() {
let p = Pattern::new(alloc::vec![Some(0x48), None, Some(0x89)]);
assert_eq!(p.len(), 3);
assert_eq!(p.as_slice()[1], None);
}
#[test]
fn pattern_is_empty() {
let p = Pattern::new(alloc::vec![]);
assert!(p.is_empty());
assert_eq!(p.len(), 0);
}
}