use crate::{MagicValueType, MimeError, MimeResult};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MimeMagicMatcher {
value_type: MagicValueType,
offset_begin: usize,
offset_end: usize,
value: Vec<u8>,
mask: Option<Vec<u8>>,
sub_matchers: Vec<MimeMagicMatcher>,
}
impl MimeMagicMatcher {
pub fn new(
value_type: MagicValueType,
offset_begin: usize,
offset_end: usize,
value: Vec<u8>,
mask: Option<Vec<u8>>,
sub_matchers: Vec<MimeMagicMatcher>,
) -> MimeResult<Self> {
validate_offsets(offset_begin, offset_end)?;
validate_value_width(value_type, &value)?;
validate_mask_width(&value, mask.as_deref())?;
Ok(Self {
value_type,
offset_begin,
offset_end,
value,
mask,
sub_matchers,
})
}
pub fn value_type(&self) -> MagicValueType {
self.value_type
}
pub fn offset_begin(&self) -> usize {
self.offset_begin
}
pub fn offset_end(&self) -> usize {
self.offset_end
}
pub fn value(&self) -> &[u8] {
&self.value
}
pub fn mask(&self) -> Option<&[u8]> {
self.mask.as_deref()
}
pub fn sub_matchers(&self) -> &[MimeMagicMatcher] {
&self.sub_matchers
}
pub fn max_test_bytes(&self) -> usize {
let own_bytes = self.offset_end.saturating_add(self.value.len());
self.sub_matchers
.iter()
.map(MimeMagicMatcher::max_test_bytes)
.max()
.map_or(own_bytes, |child_bytes| own_bytes.max(child_bytes))
}
pub fn matches(&self, bytes: &[u8]) -> bool {
let parent_matches = match self.value_type {
MagicValueType::String => self.matches_bytes(bytes, &self.value, self.mask.as_deref()),
MagicValueType::Byte => self.matches_bytes(bytes, &self.value, self.mask.as_deref()),
MagicValueType::Host16
| MagicValueType::Host32
| MagicValueType::Big16
| MagicValueType::Big32
| MagicValueType::Little16
| MagicValueType::Little32 => {
let value = ordered_numeric_bytes(self.value_type, &self.value);
let mask = self
.mask
.as_deref()
.map(|mask| ordered_numeric_bytes(self.value_type, mask));
self.matches_bytes(bytes, &value, mask.as_deref())
}
};
if !parent_matches {
return false;
}
self.sub_matchers.is_empty()
|| self
.sub_matchers
.iter()
.any(|sub_matcher| sub_matcher.matches(bytes))
}
fn matches_bytes(&self, bytes: &[u8], value: &[u8], mask: Option<&[u8]>) -> bool {
if value.is_empty() || bytes.len() < value.len() || self.offset_begin >= bytes.len() {
return false;
}
let last_possible = bytes.len() - value.len();
let end = self.offset_end.min(last_possible);
if self.offset_begin > end {
return false;
}
(self.offset_begin..=end).any(|offset| value_matches_at(bytes, offset, value, mask))
}
}
fn validate_offsets(offset_begin: usize, offset_end: usize) -> MimeResult<()> {
if offset_begin > offset_end {
return Err(MimeError::invalid_matcher(
"offset begin must not be greater than offset end",
));
}
Ok(())
}
fn validate_value_width(value_type: MagicValueType, value: &[u8]) -> MimeResult<()> {
if value.is_empty() {
return Err(MimeError::invalid_matcher(
"magic matcher value must not be empty",
));
}
if let Some(width) = value_type.numeric_width()
&& value.len() != width
{
return Err(MimeError::invalid_matcher(format!(
"{} matcher requires {width} value byte(s)",
value_type.name()
)));
}
Ok(())
}
fn validate_mask_width(value: &[u8], mask: Option<&[u8]>) -> MimeResult<()> {
if let Some(mask) = mask
&& mask.len() != value.len()
{
return Err(MimeError::invalid_matcher(
"magic matcher mask length must match value length",
));
}
Ok(())
}
fn ordered_numeric_bytes(value_type: MagicValueType, bytes: &[u8]) -> Vec<u8> {
if value_type.uses_little_endian_order() {
bytes.iter().rev().copied().collect()
} else {
bytes.to_vec()
}
}
fn value_matches_at(bytes: &[u8], offset: usize, value: &[u8], mask: Option<&[u8]>) -> bool {
match mask {
Some(mask) => {
value
.iter()
.zip(mask.iter())
.enumerate()
.all(|(index, (value_byte, mask_byte))| {
(bytes[offset + index] & mask_byte) == (*value_byte & mask_byte)
})
}
None => value
.iter()
.enumerate()
.all(|(index, value_byte)| bytes[offset + index] == *value_byte),
}
}
#[cfg(coverage)]
pub(crate) mod coverage_support {
use crate::MagicValueType;
use super::MimeMagicMatcher;
pub(crate) fn exercise_matcher_edges() -> Vec<String> {
let big16 = MimeMagicMatcher::new(
MagicValueType::Big16,
0,
0,
0x1234u16.to_be_bytes().to_vec(),
None,
vec![],
)
.expect("big16 matcher should be valid");
let big32 = MimeMagicMatcher::new(
MagicValueType::Big32,
0,
0,
0x12345678u32.to_be_bytes().to_vec(),
Some(0xfffffff0u32.to_be_bytes().to_vec()),
vec![],
)
.expect("big32 matcher should be valid");
let host16 = MimeMagicMatcher::new(
MagicValueType::Host16,
0,
0,
0x1234u16.to_be_bytes().to_vec(),
None,
vec![],
)
.expect("host16 matcher should be valid");
let host32 = MimeMagicMatcher::new(
MagicValueType::Host32,
0,
0,
0x12345678u32.to_be_bytes().to_vec(),
None,
vec![],
)
.expect("host32 matcher should be valid");
let empty_value_error =
MimeMagicMatcher::new(MagicValueType::String, 0, 0, Vec::new(), None, vec![])
.expect_err("empty value should fail")
.to_string();
let width_error = MimeMagicMatcher::new(MagicValueType::Big16, 0, 0, vec![0], None, vec![])
.expect_err("bad width should fail")
.to_string();
let mask_error = MimeMagicMatcher::new(
MagicValueType::String,
0,
0,
b"ABC".to_vec(),
Some(vec![0xff]),
vec![],
)
.expect_err("bad mask width should fail")
.to_string();
vec![
big16.value_type().name().to_owned(),
big16.offset_begin().to_string(),
big16.offset_end().to_string(),
big16.value().len().to_string(),
big16.mask().is_none().to_string(),
big16.sub_matchers().len().to_string(),
big16.matches(&0x1234u16.to_be_bytes()).to_string(),
big16.matches(&[0]).to_string(),
big32.matches(&[0x12, 0x34, 0x56, 0x70]).to_string(),
host16.matches(&ordered_host16_bytes(0x1234)).to_string(),
host32
.matches(&ordered_host32_bytes(0x12345678))
.to_string(),
empty_value_error,
width_error,
mask_error,
]
}
fn ordered_host16_bytes(value: u16) -> [u8; 2] {
if cfg!(target_endian = "little") {
value.to_le_bytes()
} else {
value.to_be_bytes()
}
}
fn ordered_host32_bytes(value: u32) -> [u8; 4] {
if cfg!(target_endian = "little") {
value.to_le_bytes()
} else {
value.to_be_bytes()
}
}
}