Skip to content

Commit

Permalink
Make sure there is a single manifest store in the asset (#114)
Browse files Browse the repository at this point in the history
* Check for more than one manifest store

* Fix grabbing wrong UUID

* Updated to fix playback issue

* Add comment
  • Loading branch information
mauricefisher64 authored Aug 24, 2022
1 parent 60112c2 commit 68d0100
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 65 deletions.
149 changes: 85 additions & 64 deletions sdk/src/asset_handlers/bmff_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))?;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion sdk/src/asset_handlers/jpeg_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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;
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions sdk/src/asset_handlers/png_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Binary file modified sdk/tests/fixtures/video1.mp4
Binary file not shown.

0 comments on commit 68d0100

Please # to comment.