use std::ops::Range;
use nom::IResult;
use super::BoxHolder;
use crate::exif::TiffHeader;
pub const UUID_SIZE: usize = 16;
const CMT_BOX_TYPES: &[&str] = &["CMT1", "CMT2", "CMT3"];
pub const CANON_UUID: [u8; 16] = [
0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, 0x48,
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CanonUuidBox {
cmt1_offset: Option<Range<usize>>,
cmt2_offset: Option<Range<usize>>,
cmt3_offset: Option<Range<usize>>,
}
impl CanonUuidBox {
pub fn exif_data_offset(&self) -> Option<&Range<usize>> {
self.cmt1_offset.as_ref()
}
#[allow(dead_code)] pub fn cmt2_data_offset(&self) -> Option<&Range<usize>> {
self.cmt2_offset.as_ref()
}
#[allow(dead_code)] pub fn cmt3_data_offset(&self) -> Option<&Range<usize>> {
self.cmt3_offset.as_ref()
}
pub fn parse<'a>(uuid_data: &'a [u8], full_input: &'a [u8]) -> IResult<&'a [u8], CanonUuidBox> {
if uuid_data.len() < UUID_SIZE {
tracing::error!(
"Canon UUID box data too small: {} bytes, expected at least {}",
uuid_data.len(),
UUID_SIZE
);
return nom::combinator::fail(uuid_data);
}
if full_input.is_empty() {
tracing::error!("Full input is empty for Canon UUID box parsing");
return nom::combinator::fail(uuid_data);
}
let mut remain = &uuid_data[UUID_SIZE..];
let mut cmt1_offset = None;
let mut cmt2_offset = None;
let mut cmt3_offset = None;
tracing::debug!(
"Parsing Canon UUID box with {} bytes of CMT data",
remain.len()
);
while !remain.is_empty() {
let (new_remain, bbox) = match BoxHolder::parse(remain) {
Ok(result) => result,
Err(e) => {
tracing::warn!(
"Failed to parse CMT box, continuing with partial data: {:?}",
e
);
break; }
};
let box_type = bbox.box_type();
if CMT_BOX_TYPES.contains(&box_type) {
let data_start = bbox.data.as_ptr() as usize;
let input_start = full_input.as_ptr() as usize;
if data_start < input_start || data_start >= input_start + full_input.len() {
tracing::warn!("CMT box data pointer outside input bounds");
remain = new_remain;
continue;
}
let start_offset = data_start - input_start;
let body_start = start_offset + bbox.header_size();
let body_end = start_offset + bbox.data.len();
if body_end > full_input.len() {
tracing::warn!(
"CMT box body extends beyond input bounds: {}..{} > {}",
body_start,
body_end,
full_input.len()
);
remain = new_remain;
continue;
}
let offset_range = body_start..body_end;
let cmt_data = &full_input[offset_range.clone()];
if !Self::validate_cmt_data(box_type, cmt_data) {
tracing::warn!("CMT box {} failed validation, skipping", box_type);
remain = new_remain;
continue;
}
match box_type {
"CMT1" => {
cmt1_offset = Some(offset_range);
tracing::debug!("Found CMT1 (IFD0) at offset {}..{}", body_start, body_end);
}
"CMT2" => {
cmt2_offset = Some(offset_range);
tracing::debug!(
"Found CMT2 (ExifIFD) at offset {}..{}",
body_start,
body_end
);
}
"CMT3" => {
cmt3_offset = Some(offset_range);
tracing::debug!(
"Found CMT3 (MakerNotes) at offset {}..{}",
body_start,
body_end
);
}
_ => unreachable!("box_type should be one of CMT1, CMT2, or CMT3"),
}
} else {
tracing::debug!("Skipping unknown box type: {}", box_type);
}
remain = new_remain;
}
Ok((
remain,
CanonUuidBox {
cmt1_offset,
cmt2_offset,
cmt3_offset,
},
))
}
fn validate_cmt_data(box_type: &str, data: &[u8]) -> bool {
if data.len() < 8 {
tracing::warn!("CMT box {} too small: {} bytes", box_type, data.len());
return false;
}
match box_type {
"CMT1" => {
if TiffHeader::parse(data).is_ok() {
tracing::debug!("CMT1 has valid TIFF header");
true
} else {
tracing::warn!("CMT1 does not have valid TIFF header");
false
}
}
"CMT2" | "CMT3" => {
if data.len() >= 8 {
tracing::debug!("CMT box {} has sufficient size", box_type);
true
} else {
tracing::warn!("CMT box {} too small for valid data", box_type);
false
}
}
_ => {
tracing::warn!("Unknown CMT box type: {}", box_type);
false
}
}
}
}