Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Make sure there is a single manifest store in the asset #114

Merged
merged 4 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
10 changes: 9 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,20 @@ 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 {
// 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.