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),
}
}