commit 68d0100946265da7e7cf82a348e9bdfc5d908d06
parent 60112c212e8f4bcd5fc715fb469bb32921f8e216
Author: mauricefisher64 <92736594+mauricefisher64@users.noreply.github.com>
Date: Wed, 24 Aug 2022 10:51:43 -0400
Make sure there is a single manifest store in the asset (#114)
* Check for more than one manifest store
* Fix grabbing wrong UUID
* Updated to fix playback issue
* Add comment
Diffstat:
5 files changed, 108 insertions(+), 65 deletions(-)
diff --git a/sdk/src/asset_handlers/bmff_io.rs b/sdk/src/asset_handlers/bmff_io.rs
@@ -769,6 +769,28 @@ pub(crate) fn build_bmff_tree(
Ok(())
}
+fn get_manifest_token(
+ bmff_tree: &Arena<BoxInfo>,
+ bmff_map: &HashMap<String, Vec<Token>>,
+) -> Option<Token> {
+ if let Some(uuid_list) = bmff_map.get("/uuid") {
+ for uuid_token in uuid_list {
+ let box_info = &bmff_tree[*uuid_token];
+
+ // make sure it is UUID box
+ if box_info.data.box_type == BoxType::UuidBox {
+ if let Some(uuid) = &box_info.data.user_type {
+ // make sure it is a C2PA ContentProvenanceBox box
+ if vec_compare(&C2PA_UUID, uuid) {
+ return Some(*uuid_token);
+ }
+ }
+ }
+ }
+ }
+ None
+}
+
impl CAILoader for BmffIO {
fn read_cai(&self, reader: &mut dyn CAIRead) -> Result<Vec<u8>> {
let start = reader.seek(SeekFrom::Current(0))?;
@@ -793,71 +815,81 @@ impl CAILoader for BmffIO {
// build layout of the BMFF structure
build_bmff_tree(reader, size, &mut bmff_tree, &root_token, &mut bmff_map)?;
+ let mut output: Option<Vec<u8>> = None;
+
// grab top level (for now) C2PA box
if let Some(uuid_list) = bmff_map.get("/uuid") {
- let uuid_token = &uuid_list[0];
- let box_info = &bmff_tree[*uuid_token];
+ let mut manifest_store_cnt = 0;
- // make sure it is UUID box
- if box_info.data.box_type == BoxType::UuidBox {
- if let Some(uuid) = &box_info.data.user_type {
- // make sure it is a C2PA ContentProvenanceBox box
- if vec_compare(&C2PA_UUID, uuid) {
- let mut data_len = box_info.data.size - HEADER_SIZE - 16 /*UUID*/;
-
- // set reader to start of box contents
- skip_bytes_to(reader, box_info.data.offset + HEADER_SIZE + 16)?;
-
- // Fullbox => 8 bits for version 24 bits for flags
- let (_version, _flags) = read_box_header_ext(reader)?;
- data_len -= 4;
-
- // get the purpose
- let mut purpose = Vec::with_capacity(64);
- loop {
- let mut buf = [0; 1];
- reader.read_exact(&mut buf)?;
- data_len -= 1;
- if buf[0] == 0x00 {
- break;
- } else {
- purpose.push(buf[0]);
- }
- }
+ for uuid_token in uuid_list {
+ let box_info = &bmff_tree[*uuid_token];
- // is the purpose manifest?
- if vec_compare(&purpose, MANIFEST.as_bytes()) {
- // offset to first aux uuid with purpose merkle
- let mut buf = [0u8; 8];
- reader.read_exact(&mut buf)?;
- data_len -= 8;
+ // make sure it is UUID box
+ if box_info.data.box_type == BoxType::UuidBox {
+ if let Some(uuid) = &box_info.data.user_type {
+ // make sure it is a C2PA ContentProvenanceBox box
+ if vec_compare(&C2PA_UUID, uuid) {
+ let mut data_len = box_info.data.size - HEADER_SIZE - 16 /*UUID*/;
- // offset to first aux uuid
- let offset = u64::from_be_bytes(buf);
+ // set reader to start of box contents
+ skip_bytes_to(reader, box_info.data.offset + HEADER_SIZE + 16)?;
- // if no offset this contains the manifest
- if offset == 0 {
- let mut buf = vec![0u8; data_len as usize];
- reader.read_exact(&mut buf)?;
+ // Fullbox => 8 bits for version 24 bits for flags
+ let (_version, _flags) = read_box_header_ext(reader)?;
+ data_len -= 4;
- return Ok(buf);
- } else {
- // handle aux uuids
- let mut buf = vec![0u8; data_len as usize];
+ // get the purpose
+ let mut purpose = Vec::with_capacity(64);
+ loop {
+ let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
+ data_len -= 1;
+ if buf[0] == 0x00 {
+ break;
+ } else {
+ purpose.push(buf[0]);
+ }
+ }
- let _mm: BmffMerkleMap = serde_cbor::from_slice(&buf)?;
+ // is the purpose manifest?
+ if vec_compare(&purpose, MANIFEST.as_bytes()) {
+ // offset to first aux uuid with purpose merkle
+ let mut buf = [0u8; 8];
+ reader.read_exact(&mut buf)?;
+ data_len -= 8;
+
+ // offset to first aux uuid
+ let offset = u64::from_be_bytes(buf);
+
+ // if no offset this contains the manifest
+ if offset == 0 {
+ if manifest_store_cnt == 0 {
+ let mut manifest = vec![0u8; data_len as usize];
+ reader.read_exact(&mut manifest)?;
+ output = Some(manifest);
+
+ manifest_store_cnt += 1;
+ } else {
+ return Err(Error::TooManyManifestStores);
+ }
+ } else {
+ // handle aux uuids
+ let mut buf = vec![0u8; data_len as usize];
+ reader.read_exact(&mut buf)?;
+
+ let _mm: BmffMerkleMap = serde_cbor::from_slice(&buf)?;
+ }
+ } else if vec_compare(&purpose, MERKLE.as_bytes()) {
+ // handle merkle boxes not yet handled
+ return Err(Error::UnsupportedType);
}
- } else if vec_compare(&purpose, MERKLE.as_bytes()) {
- // handle merkle boxes not yet handled
- return Err(Error::UnsupportedType);
}
}
}
}
}
- Err(Error::JumbfNotFound)
+ output.ok_or(Error::JumbfNotFound)
}
// Get XMP block
@@ -912,25 +944,14 @@ impl AssetIO for BmffIO {
let ftyp_size = ftyp_info.size;
// get position to insert c2pa
- let (c2pa_start, c2pa_length) = if let Some(uuid_tokens) = bmff_map.get("/uuid") {
- let uuid_info = &bmff_tree[uuid_tokens[0]].data;
-
- // is this a C2PA manifest
- let is_c2pa = if let Some(uuid) = &uuid_info.user_type {
- // make sure it is a C2PA box
- vec_compare(&C2PA_UUID, uuid)
- } else {
- false
- };
+ let (c2pa_start, c2pa_length) =
+ if let Some(c2pa_token) = get_manifest_token(&bmff_tree, &bmff_map) {
+ let uuid_info = &bmff_tree[c2pa_token].data;
- if is_c2pa {
(uuid_info.offset, Some(uuid_info.size))
} else {
((ftyp_offset + ftyp_size), None)
- }
- } else {
- ((ftyp_offset + ftyp_size), None)
- };
+ };
let mut new_c2pa_box: Vec<u8> = Vec::with_capacity(store_bytes.len() * 2);
let merkle_data: &[u8] = &[]; // not yet supported
diff --git a/sdk/src/asset_handlers/jpeg_io.rs b/sdk/src/asset_handlers/jpeg_io.rs
@@ -147,6 +147,8 @@ impl CAILoader for JpegIO {
fn read_cai(&self, asset_reader: &mut dyn CAIRead) -> Result<Vec<u8>> {
let mut buffer: Vec<u8> = Vec::new();
+ let mut manifest_store_cnt = 0;
+
// load the bytes
let mut buf: Vec<u8> = Vec::new();
asset_reader.read_to_end(&mut buf).map_err(Error::IoError)?;
@@ -184,14 +186,21 @@ impl CAILoader for JpegIO {
buffer.append(&mut raw_vec.as_mut_slice()[16..].to_vec());
cai_seg_cnt += 1;
- } else {
+ } else if raw_vec.len() > 28 {
+ // must be at least 28 bytes for this to be a valid JUMBF box
// check if this is a CAI JUMBF block
let jumb_type = raw_vec.as_mut_slice()[24..28].to_vec();
let is_cai = vec_compare(&C2PA_MARKER, &jumb_type);
if is_cai {
+ if manifest_store_cnt == 1 {
+ return Err(Error::TooManyManifestStores);
+ }
+
buffer.append(&mut raw_vec.as_mut_slice()[8..].to_vec());
cai_seg_cnt = 1;
cai_en = en.clone(); // store the identifier
+
+ manifest_store_cnt += 1;
}
}
}
diff --git a/sdk/src/asset_handlers/png_io.rs b/sdk/src/asset_handlers/png_io.rs
@@ -110,6 +110,16 @@ fn get_png_chunk_positions(f: &mut dyn CAIRead) -> Result<Vec<PngChunkPos>> {
fn get_cai_data(f: &mut dyn CAIRead) -> Result<Vec<u8>> {
let ps = get_png_chunk_positions(f)?;
+ if ps
+ .clone()
+ .into_iter()
+ .filter(|pcp| pcp.name == CAI_CHUNK)
+ .count()
+ > 1
+ {
+ return Err(Error::TooManyManifestStores);
+ }
+
let pcp = ps
.into_iter()
.find(|pcp| pcp.name == CAI_CHUNK)
diff --git a/sdk/src/error.rs b/sdk/src/error.rs
@@ -81,6 +81,9 @@ pub enum Error {
#[error("update manifest is invalid")]
UpdateManifestInvalid,
+ #[error("more than one manifest store detected")]
+ TooManyManifestStores,
+
/// The COSE Sign1 structure can not be parsed.
#[error("COSE Sign1 structure can not be parsed: {coset_error}")]
InvalidCoseSignature {
diff --git a/sdk/tests/fixtures/video1.mp4 b/sdk/tests/fixtures/video1.mp4
Binary files differ.