pub use ifd::Ifd;
pub use raves_metadata_types::exif::{Endianness, Field, FieldData, primitives::*};
use winnow::{
Parser as _, Stateful,
binary::{Endianness as WinnowEndianness, u16, u32},
error::EmptyError,
token::take,
};
use crate::exif::ifd::RECURSION_LIMIT;
use self::{
error::{ExifFatalError, ExifFatalResult},
ifd::parse_ifd,
};
use raves_metadata_types::exif::ifd::IfdGroup;
pub mod error;
mod ifd;
mod value;
#[derive(Clone, Debug, PartialEq, PartialOrd, Hash)]
pub struct Exif {
pub endianness: Endianness,
pub ifds: Vec<Ifd>,
}
impl Exif {
pub fn new(input: &mut &[u8]) -> ExifFatalResult<Self> {
#[expect(
suspicious_double_ref_op,
reason = "we want to save the original slice (\"blob\") for absolute offsets"
)]
let blob: &[u8] = input.clone();
let endianness: Endianness = parse_blob_endianness.parse_next(input)?;
let winnow_endianness: WinnowEndianness = match endianness {
Endianness::Little => WinnowEndianness::Little,
Endianness::Big => WinnowEndianness::Big,
};
let stateful_input = &mut Stream {
input,
state: State {
blob,
current_ifd: IfdGroup::_0, endianness: &winnow_endianness,
recursion_ct: 0,
recursion_stack: [None; RECURSION_LIMIT as usize],
},
};
parse_tiff_magic_number.parse_next(stateful_input)?;
let offset: u32 = parse_tiff_header_offset(stateful_input)?;
take(offset)
.parse_next(&mut stateful_input.input)
.inspect_err(|_| log::error!("Failed to skip to IFDs"))
.map_err(|_: EmptyError| ExifFatalError::NotEnoughDataForHeaderOffset)?;
let mut ifds: Vec<Ifd> = Vec::new();
if stateful_input.is_empty() {
log::trace!("There's no more input. Assuming there are zero IFDs.");
return Ok(Self { endianness, ifds });
}
let (first_ifd, mut maybe_next_ifd_ptr): (Ifd, Option<u32>) =
parse_ifd.parse_next(stateful_input).inspect_err(|e| {
log::error!("Failed to parse Exif! The first IFD failed to parse! err: {e}")
})?;
log::trace!("Completed first IFD! ptr: {maybe_next_ifd_ptr:?}");
ifds.push(first_ifd);
while let Some(next_ifd_ptr) = maybe_next_ifd_ptr {
log::trace!("At next IFD! index: `{next_ifd_ptr}`");
stateful_input.input = &blob[(next_ifd_ptr as usize)..];
stateful_input.state.recursion_ct = 0;
stateful_input.state.recursion_stack = Default::default();
let (ifd, ptr) = parse_ifd.parse_next(stateful_input)?;
ifds.push(ifd);
maybe_next_ifd_ptr = ptr;
}
Ok(Self { endianness, ifds })
}
}
fn parse_blob_endianness(input: &mut &[u8]) -> ExifFatalResult<Endianness> {
let input_len = input.len();
log::trace!("Looking for the BOM bytes...");
let two_ascii_bytes: [u8; 2] = take(2_usize)
.parse_next(input)
.map_err(|_: EmptyError| {
log::error!("Couldn't find endianness marker!");
ExifFatalError::NoByteOrderMarker {
len: input_len as u8,
}
})?
.try_into()
.unwrap_or_else(|e| unreachable!("winnow verified the size. but err: {e}"));
log::trace!("Found two BOM bytes!");
log::trace!("Grabbing BOM...");
match two_ascii_bytes {
[b'I', b'I'] => Ok(Endianness::Little).inspect(|f| log::trace!("It's LE: {f:?}")),
[b'M', b'M'] => Ok(Endianness::Big).inspect(|f| log::trace!("It's BE: {f:?}")),
found => {
let e = ExifFatalError::WeirdByteOrderMarker { found };
log::error!("Couldn't parse out Exif! err: {e}");
Err(e)
}
}
}
#[derive(Debug)]
struct State<'a> {
blob: &'a [u8],
current_ifd: IfdGroup,
endianness: &'a WinnowEndianness,
recursion_ct: u8,
recursion_stack: [Option<u32>; RECURSION_LIMIT as usize],
}
type Stream<'s> = Stateful<&'s [u8], State<'s>>;
fn parse_tiff_magic_number(input: &mut Stream) -> ExifFatalResult<()> {
let endianness = input.state.endianness;
log::trace!("Getting magic number...");
let magic_number: u16 = u16(*endianness)
.parse_next(input)
.map_err(|_: EmptyError| {
log::error!("Couldn't find TIFF magic number!");
ExifFatalError::NoTiffMagicNumber
})?;
log::trace!("Checking magic number...");
if magic_number != 42 {
log::error!("Magic number wasn't for TIFF. got: `{magic_number}`");
return Err(ExifFatalError::MagicNumberWasntTiff {
found: magic_number,
});
}
log::trace!("Magic number was good!");
Ok(())
}
fn parse_tiff_header_offset(input: &mut Stream) -> ExifFatalResult<u32> {
let endianness = input.state.endianness;
u32(*endianness)
.parse_next(&mut input.input)
.map_err(|_: EmptyError| {
log::error!("Didn't find a TIFF header offset!");
ExifFatalError::NoTiffHeaderOffset
})
.inspect(|offset| log::trace!("found offset: `{offset}`"))?
.checked_sub(8_u32)
.ok_or_else(|| {
log::error!("Exif blob placed offset out of bounds! Can't continue parsing.");
ExifFatalError::HeaderOffsetBeforeHeader
})
}
type NextIfdPointer = Option<u32>;
#[cfg(test)]
mod tests {
use raves_metadata_types::exif::{
Endianness, Field, FieldData, FieldTag,
ifd::IfdGroup,
primitives::{Primitive, PrimitiveCount, PrimitiveTy, Rational},
tags::{Ifd0Tag, KnownTag},
};
use winnow::binary::Endianness as WinnowEndianness;
use crate::{
exif::{
Exif, Ifd, error::ExifFatalError, parse_blob_endianness, parse_tiff_header_offset,
parse_tiff_magic_number,
},
util::logger,
};
#[test]
fn endianness() {
_ = env_logger::builder()
.filter_level(log::LevelFilter::max())
.format_file(true)
.format_line_number(true)
.try_init();
assert_eq!(
parse_blob_endianness(&mut b"II".as_slice()),
Ok(Endianness::Little)
);
assert_eq!(
parse_blob_endianness(&mut b"MM".as_slice()),
Ok(Endianness::Big)
);
assert!(
parse_blob_endianness(&mut b"other".as_slice()).is_err(),
"other strings aren't indicative of endianness"
);
}
#[test]
fn tiff_header() {
_ = env_logger::builder()
.filter_level(log::LevelFilter::max())
.format_file(true)
.format_line_number(true)
.try_init();
let backing_bytes = {
let mut v = Vec::new();
v.append(&mut b"II".to_vec());
v.push(0x2a);
v.push(0x00);
v
};
let bytes = &mut backing_bytes.as_slice();
let endianness = parse_blob_endianness(bytes);
assert_eq!(endianness, Ok(Endianness::Little));
log::info!("backing bytes now: {backing_bytes:#?}");
assert_eq!(
parse_tiff_magic_number(&mut super::Stream {
state: super::State {
current_ifd: IfdGroup::_0,
endianness: &WinnowEndianness::Little,
blob: backing_bytes.as_slice(),
recursion_ct: 0,
recursion_stack: Default::default(),
},
input: bytes
}),
Ok(()),
"should find header"
);
}
#[test]
fn tiff_header_offset() {
_ = env_logger::builder()
.filter_level(log::LevelFilter::max())
.format_file(true)
.format_line_number(true)
.try_init();
let backing_bytes = {
let mut v = Vec::new();
v.extend_from_slice(b"II".as_slice());
v.push(0x2a);
v.push(0x00);
v.extend_from_slice(8_u32.to_le_bytes().as_slice());
v
};
let bytes = &mut backing_bytes.as_slice();
let endianness = parse_blob_endianness(bytes);
assert_eq!(endianness, Ok(Endianness::Little));
log::info!("backing bytes now: {backing_bytes:#?}");
let stream = &mut super::Stream {
state: super::State {
current_ifd: IfdGroup::_0,
endianness: &WinnowEndianness::Little,
blob: backing_bytes.as_slice(),
recursion_ct: 0,
recursion_stack: Default::default(),
},
input: bytes,
};
assert_eq!(parse_tiff_magic_number(stream), Ok(()));
assert_eq!(parse_tiff_header_offset(stream), Ok(0_u32));
assert_eq!(
parse_tiff_header_offset(&mut super::Stream {
state: super::State {
current_ifd: IfdGroup::_0,
endianness: &WinnowEndianness::Little,
blob: backing_bytes.as_slice(),
recursion_ct: 0,
recursion_stack: Default::default(),
},
input: 7_u32.to_le_bytes().as_slice(),
}),
Err(ExifFatalError::HeaderOffsetBeforeHeader),
);
assert_eq!(
parse_tiff_header_offset(&mut super::Stream {
state: super::State {
current_ifd: IfdGroup::_0,
endianness: &WinnowEndianness::Little,
blob: backing_bytes.as_slice(),
recursion_ct: 0,
recursion_stack: Default::default(),
},
input: 0_u32.to_le_bytes().as_slice(),
}),
Err(ExifFatalError::HeaderOffsetBeforeHeader),
);
}
#[test]
fn parses_minimal_exif() {
_ = env_logger::builder()
.filter_level(log::LevelFilter::max())
.format_file(true)
.format_line_number(true)
.try_init();
let mut backing_bytes = Vec::new();
backing_bytes.extend_from_slice(b"II");
backing_bytes.extend_from_slice(42_u16.to_le_bytes().as_slice());
backing_bytes.extend_from_slice(9_u32.to_le_bytes().as_slice()); backing_bytes.push(u8::MAX);
backing_bytes.extend_from_slice(1_u16.to_le_bytes().as_slice());
backing_bytes.extend_from_slice(
KnownTag::Ifd0Tag(Ifd0Tag::ImageWidth)
.tag_id()
.to_le_bytes()
.as_slice(),
);
backing_bytes.extend_from_slice(3_u16.to_le_bytes().as_slice());
backing_bytes.extend_from_slice(1_u32.to_le_bytes().as_slice());
backing_bytes.extend_from_slice(1920_u16.to_le_bytes().as_slice());
backing_bytes.extend_from_slice(0_u16.to_le_bytes().as_slice());
backing_bytes.extend_from_slice(0_u32.to_le_bytes().as_slice());
let mut bytes = backing_bytes.as_slice();
let exif = Exif::new(&mut bytes).unwrap();
assert_eq!(exif.ifds.len(), 1, "only one IFD");
let ifd0 = &exif.ifds[0];
let Ok(ref field) = ifd0.fields[0] else {
panic!("should have a field");
};
assert_eq!(
field.tag,
FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::ImageWidth)),
"field tag"
);
assert_eq!(
field.data,
FieldData::Primitive(Primitive::Short(1920)),
"field val"
);
}
#[test]
fn ifd_with_no_fields_should_fail() {
let mut backing_bytes = Vec::new();
backing_bytes.extend_from_slice(b"MM");
backing_bytes.extend_from_slice(42_u16.to_be_bytes().as_slice());
backing_bytes.extend_from_slice(8_u32.to_be_bytes().as_slice());
backing_bytes.extend_from_slice(0_u16.to_be_bytes().as_slice());
backing_bytes.extend_from_slice(0_u32.to_le_bytes().as_slice());
let parsed = Exif::new(&mut backing_bytes.as_slice());
assert_eq!(parsed, Err(ExifFatalError::IfdHadZeroFields))
}
#[test]
fn no_ifds() {
_ = env_logger::builder()
.filter_level(log::LevelFilter::max())
.format_file(true)
.format_line_number(true)
.try_init();
let mut backing_bytes = Vec::new();
backing_bytes.extend_from_slice(b"II");
backing_bytes.extend_from_slice(42_u16.to_le_bytes().as_slice());
backing_bytes.extend_from_slice(8_u32.to_le_bytes().as_slice());
let parsed = Exif::new(&mut backing_bytes.as_slice());
assert_eq!(
parsed,
Ok(Exif {
endianness: Endianness::Little,
ifds: vec![]
}),
"we shouldn't find any IFDs"
);
}
#[test]
fn multiple_ifds() {
_ = env_logger::builder()
.filter_level(log::LevelFilter::max())
.format_file(true)
.format_line_number(true)
.try_init();
let mut backing_bytes = Vec::new();
backing_bytes.extend_from_slice(b"MM");
backing_bytes.extend_from_slice(42_u16.to_be_bytes().as_slice());
backing_bytes.extend_from_slice(8_u32.to_be_bytes().as_slice());
backing_bytes.extend_from_slice(2_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(
KnownTag::Ifd0Tag(Ifd0Tag::ImageWidth)
.tag_id()
.to_be_bytes()
.as_slice(),
); backing_bytes.extend_from_slice(3_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(1_u32.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(1920_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(0_u16.to_be_bytes().as_slice());
backing_bytes.extend_from_slice(
KnownTag::Ifd0Tag(Ifd0Tag::ImageLength)
.tag_id()
.to_be_bytes()
.as_slice(),
); backing_bytes.extend_from_slice(3_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(1_u32.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(1080_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(0_u16.to_be_bytes().as_slice());
let next_ifd_offset = backing_bytes.len() as u32 + 4 + 88;
backing_bytes.extend_from_slice(next_ifd_offset.to_be_bytes().as_slice());
backing_bytes.extend_from_slice([0_u8; 88].as_slice());
backing_bytes.extend_from_slice(1_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(
KnownTag::Ifd0Tag(Ifd0Tag::TransferFunction)
.tag_id()
.to_be_bytes()
.as_slice(),
); backing_bytes.extend_from_slice(
(KnownTag::Ifd0Tag(Ifd0Tag::TransferFunction).types()[0] as u16)
.to_be_bytes()
.as_slice(),
); backing_bytes.extend_from_slice(
{
let PrimitiveCount::Known(c) = KnownTag::Ifd0Tag(Ifd0Tag::TransferFunction).count()
else {
panic!("wrong count");
};
c
}
.to_be_bytes()
.as_slice(),
); let ifd2_f1_data = [99_u8; (3 * 256_usize) * 2].as_slice();
backing_bytes.extend_from_slice(2000_u32.to_be_bytes().as_slice());
let next_ifd_offset = backing_bytes.len() as u32 + 4 + 88;
backing_bytes.extend_from_slice(next_ifd_offset.to_be_bytes().as_slice());
backing_bytes.extend_from_slice([0_u8; 88].as_slice());
backing_bytes.extend_from_slice(2_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(
KnownTag::Ifd0Tag(Ifd0Tag::ImageWidth)
.tag_id()
.to_be_bytes()
.as_slice(),
); backing_bytes.extend_from_slice(3_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(1_u32.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(1920_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(0_u16.to_be_bytes().as_slice());
backing_bytes.extend_from_slice(
KnownTag::Ifd0Tag(Ifd0Tag::ImageLength)
.tag_id()
.to_be_bytes()
.as_slice(),
); backing_bytes.extend_from_slice(3_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(1_u32.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(1080_u16.to_be_bytes().as_slice()); backing_bytes.extend_from_slice(0_u16.to_be_bytes().as_slice());
backing_bytes.extend_from_slice(0_u32.to_be_bytes().as_slice());
backing_bytes.extend(
(0..(2000 - backing_bytes.len()))
.map(|_| 0_u8)
.collect::<Vec<u8>>(),
);
backing_bytes.extend_from_slice(ifd2_f1_data);
let parsed = Exif::new(&mut backing_bytes.as_slice()).expect("parsing should work");
assert_eq!(
parsed,
Exif {
endianness: Endianness::Big,
ifds: vec![
Ifd {
fields: vec![
Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::ImageWidth)),
data: FieldData::Primitive(Primitive::Short(1920)),
}),
Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::ImageLength)),
data: FieldData::Primitive(Primitive::Short(1080)),
})
],
sub_ifds: Vec::new(),
},
Ifd {
fields: vec![Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::TransferFunction)),
data: FieldData::List {
list: [Primitive::Short((99_u16 << 8) | 99_u16); (3 * 256_usize)]
.into(),
ty: PrimitiveTy::Short
},
})],
sub_ifds: Vec::new(),
},
Ifd {
fields: vec![
Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::ImageWidth)),
data: FieldData::Primitive(Primitive::Short(1920)),
}),
Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::ImageLength)),
data: FieldData::Primitive(Primitive::Short(1080)),
})
],
sub_ifds: Vec::new(),
},
]
}
)
}
#[test]
fn real_file() {
logger();
let bytes = include_bytes!("../../assets/exif/1343_exif.bin");
let exif = super::Exif::new(&mut bytes.as_slice());
let expected_exif = Ok(Exif {
endianness: Endianness::Big,
ifds: vec![Ifd {
fields: vec![
Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::ImageDescription)),
data: FieldData::List {
list: b"Can you read me?\0"
.iter()
.map(|c| Primitive::Ascii(*c))
.collect(),
ty: PrimitiveTy::Ascii,
},
}),
Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::XResolution)),
data: FieldData::Primitive(Primitive::Rational(Rational {
numerator: 72,
denominator: 1,
})),
}),
Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::YResolution)),
data: FieldData::Primitive(Primitive::Rational(Rational {
numerator: 72,
denominator: 1,
})),
}),
Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::ResolutionUnit)),
data: FieldData::Primitive(Primitive::Short(2_u16)), }),
Ok(Field {
tag: FieldTag::Known(KnownTag::Ifd0Tag(Ifd0Tag::YCbCrPositioning)),
data: FieldData::Primitive(Primitive::Short(1_u16)), }),
],
sub_ifds: Vec::new(),
}],
});
assert_eq!(exif, expected_exif)
}
}