c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

bmff_io.rs (70190B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 use std::{
     15     cmp::min,
     16     collections::HashMap,
     17     fs::{File, OpenOptions},
     18     io::{Cursor, Read, Seek, SeekFrom, Write},
     19     path::Path,
     20 };
     21 
     22 use atree::{Arena, Token};
     23 use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
     24 use conv::ValueFrom;
     25 use tempfile::Builder;
     26 
     27 use crate::{
     28     assertions::{BmffMerkleMap, ExclusionsMap},
     29     asset_io::{
     30         rename_or_copy, AssetIO, AssetPatch, CAIRead, CAIReadWrite, CAIReader, CAIWriter,
     31         HashObjectPositions, RemoteRefEmbed, RemoteRefEmbedType,
     32     },
     33     error::{Error, Result},
     34     utils::{
     35         hash_utils::{vec_compare, HashRange},
     36         xmp_inmemory_utils::{add_provenance, MIN_XMP},
     37     },
     38 };
     39 
     40 pub struct BmffIO {
     41     #[allow(dead_code)]
     42     bmff_format: String, // can be used for specialized BMFF cases
     43 }
     44 
     45 const HEADER_SIZE: u64 = 8; // 4 byte type + 4 byte size
     46 const HEADER_SIZE_LARGE: u64 = 16; // 4 byte type + 4 byte size + 8 byte large size
     47 
     48 const C2PA_UUID: [u8; 16] = [
     49     0xd8, 0xfe, 0xc3, 0xd6, 0x1b, 0x0e, 0x48, 0x3c, 0x92, 0x97, 0x58, 0x28, 0x87, 0x7e, 0xc4, 0x81,
     50 ];
     51 const XMP_UUID: [u8; 16] = [
     52     0xbe, 0x7a, 0xcf, 0xcb, 0x97, 0xa9, 0x42, 0xe8, 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac,
     53 ];
     54 const MANIFEST: &str = "manifest";
     55 const MERKLE: &str = "merkle";
     56 
     57 // ISO IEC 14496-12_2022 FullBoxes
     58 const FULL_BOX_TYPES: &[&str; 80] = &[
     59     "pdin", "mvhd", "tkhd", "mdhd", "hdlr", "nmhd", "elng", "stsd", "stdp", "stts", "ctts", "cslg",
     60     "stss", "stsh", "stdp", "elst", "dref", "stsz", "stz2", "stsc", "stco", "co64", "padb", "subs",
     61     "saiz", "saio", "mehd", "trex", "mfhd", "tfhd", "trun", "tfra", "mfro", "tfdt", "leva", "trep",
     62     "assp", "sbgp", "sgpd", "csgp", "cprt", "tsel", "kind", "meta", "xml ", "bxml", "iloc", "pitm",
     63     "ipro", "infe", "iinf", "iref", "ipma", "schm", "fiin", "fpar", "fecr", "gitn", "fire", "stri",
     64     "stsg", "stvi", "csch", "sidx", "ssix", "prft", "srpp", "vmhd", "smhd", "srat", "chnl", "dmix",
     65     "txtC", "mime", "uri ", "uriI", "hmhd", "sthd", "vvhd", "medc",
     66 ];
     67 
     68 static SUPPORTED_TYPES: [&str; 14] = [
     69     "avif",
     70     "heif",
     71     "heic",
     72     "mp4",
     73     "m4a",
     74     "mov",
     75     "application/mp4",
     76     "audio/mp4",
     77     "image/avif",
     78     "image/heic",
     79     "image/heif",
     80     "video/mp4",
     81     "video/quicktime",
     82     "cr3",
     83 ];
     84 
     85 macro_rules! boxtype {
     86     ($( $name:ident => $value:expr ),*) => {
     87         #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     88         pub enum BoxType {
     89             $( $name, )*
     90             UnknownBox(u32),
     91         }
     92 
     93         impl From<u32> for BoxType {
     94             fn from(t: u32) -> BoxType {
     95                 match t {
     96                     $( $value => BoxType::$name, )*
     97                     _ => BoxType::UnknownBox(t),
     98                 }
     99             }
    100         }
    101 
    102         impl From<BoxType> for u32 {
    103             fn from(t: BoxType) -> u32 {
    104                 match t {
    105                     $( BoxType::$name => $value, )*
    106                     BoxType::UnknownBox(t) => t,
    107                 }
    108             }
    109         }
    110     }
    111 }
    112 
    113 boxtype! {
    114     Empty => 0x0000_0000,
    115     UuidBox => 0x75756964,
    116     FtypBox => 0x66747970,
    117     MvhdBox => 0x6d766864,
    118     MfhdBox => 0x6d666864,
    119     FreeBox => 0x66726565,
    120     MdatBox => 0x6d646174,
    121     MoovBox => 0x6d6f6f76,
    122     MvexBox => 0x6d766578,
    123     MehdBox => 0x6d656864,
    124     TrexBox => 0x74726578,
    125     EmsgBox => 0x656d7367,
    126     MoofBox => 0x6d6f6f66,
    127     TkhdBox => 0x746b6864,
    128     TfhdBox => 0x74666864,
    129     EdtsBox => 0x65647473,
    130     MdiaBox => 0x6d646961,
    131     ElstBox => 0x656c7374,
    132     MfraBox => 0x6d667261,
    133     MdhdBox => 0x6d646864,
    134     HdlrBox => 0x68646c72,
    135     MinfBox => 0x6d696e66,
    136     VmhdBox => 0x766d6864,
    137     StblBox => 0x7374626c,
    138     StsdBox => 0x73747364,
    139     SttsBox => 0x73747473,
    140     CttsBox => 0x63747473,
    141     StssBox => 0x73747373,
    142     StscBox => 0x73747363,
    143     StszBox => 0x7374737A,
    144     StcoBox => 0x7374636F,
    145     Co64Box => 0x636F3634,
    146     TrakBox => 0x7472616b,
    147     TrafBox => 0x74726166,
    148     TrefBox => 0x74726566,
    149     TregBox => 0x74726567,
    150     TrunBox => 0x7472756E,
    151     UdtaBox => 0x75647461,
    152     DinfBox => 0x64696e66,
    153     DrefBox => 0x64726566,
    154     UrlBox  => 0x75726C20,
    155     SmhdBox => 0x736d6864,
    156     Avc1Box => 0x61766331,
    157     AvcCBox => 0x61766343,
    158     Hev1Box => 0x68657631,
    159     HvcCBox => 0x68766343,
    160     Mp4aBox => 0x6d703461,
    161     EsdsBox => 0x65736473,
    162     Tx3gBox => 0x74783367,
    163     VpccBox => 0x76706343,
    164     Vp09Box => 0x76703039,
    165     MetaBox => 0x6D657461,
    166     SchiBox => 0x73636869,
    167     IlocBox => 0x696C6F63
    168 }
    169 
    170 struct BoxHeaderLite {
    171     pub name: BoxType,
    172     pub size: u64,
    173     pub fourcc: String,
    174     pub large_size: bool,
    175 }
    176 
    177 impl BoxHeaderLite {
    178     pub fn new(name: BoxType, size: u64, fourcc: &str) -> Self {
    179         Self {
    180             name,
    181             size,
    182             fourcc: fourcc.to_string(),
    183             large_size: false,
    184         }
    185     }
    186 
    187     pub fn read<R: Read + ?Sized>(reader: &mut R) -> Result<Self> {
    188         // Create and read to buf.
    189         let mut buf = [0u8; 8]; // 8 bytes for box header.
    190         reader.read_exact(&mut buf)?;
    191 
    192         // Get size.
    193         let mut s = [0u8; 4];
    194         s.clone_from_slice(&buf[0..4]);
    195         let size = u32::from_be_bytes(s);
    196 
    197         // Get box type string.
    198         let mut t = [0u8; 4];
    199         t.clone_from_slice(&buf[4..8]);
    200         let fourcc = String::from_utf8_lossy(&buf[4..8]).to_string();
    201         let typ = u32::from_be_bytes(t);
    202 
    203         // Get largesize if size is 1
    204         if size == 1 {
    205             reader.read_exact(&mut buf)?;
    206             let largesize = u64::from_be_bytes(buf);
    207 
    208             Ok(BoxHeaderLite {
    209                 name: BoxType::from(typ),
    210                 size: largesize,
    211                 fourcc,
    212                 large_size: true,
    213             })
    214         } else {
    215             Ok(BoxHeaderLite {
    216                 name: BoxType::from(typ),
    217                 size: size as u64,
    218                 fourcc,
    219                 large_size: false,
    220             })
    221         }
    222     }
    223 
    224     pub fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
    225         if self.size > u32::MAX as u64 {
    226             writer.write_u32::<BigEndian>(1)?;
    227             writer.write_u32::<BigEndian>(self.name.into())?;
    228             writer.write_u64::<BigEndian>(self.size)?;
    229             Ok(16)
    230         } else {
    231             writer.write_u32::<BigEndian>(self.size as u32)?;
    232             writer.write_u32::<BigEndian>(self.name.into())?;
    233             Ok(8)
    234         }
    235     }
    236 }
    237 
    238 fn write_box_uuid_extension<W: Write>(w: &mut W, uuid: &[u8; 16]) -> Result<u64> {
    239     w.write_all(uuid)?;
    240     Ok(16)
    241 }
    242 
    243 #[derive(Clone, Debug, PartialEq)]
    244 pub(crate) struct BoxInfo {
    245     path: String,
    246     parent: Option<Token>,
    247     pub offset: u64,
    248     pub size: u64,
    249     box_type: BoxType,
    250     user_type: Option<Vec<u8>>,
    251     version: Option<u8>,
    252     flags: Option<u32>,
    253 }
    254 
    255 #[derive(Clone, Debug, PartialEq)]
    256 pub(crate) struct BoxInfoLite {
    257     pub path: String,
    258     pub offset: u64,
    259     pub size: u64,
    260 }
    261 
    262 fn read_box_header_ext<R: Read + Seek + ?Sized>(reader: &mut R) -> Result<(u8, u32)> {
    263     let version = reader.read_u8()?;
    264     let flags = reader.read_u24::<BigEndian>()?;
    265     Ok((version, flags))
    266 }
    267 fn write_box_header_ext<W: Write>(w: &mut W, v: u8, f: u32) -> Result<u64> {
    268     w.write_u8(v)?;
    269     w.write_u24::<BigEndian>(f)?;
    270     Ok(4)
    271 }
    272 
    273 fn box_start<R: Read + Seek + ?Sized>(reader: &mut R, is_large: bool) -> Result<u64> {
    274     if is_large {
    275         Ok(reader.stream_position()? - HEADER_SIZE_LARGE)
    276     } else {
    277         Ok(reader.stream_position()? - HEADER_SIZE)
    278     }
    279 }
    280 
    281 fn _skip_bytes<R: Read + Seek + ?Sized>(reader: &mut R, size: u64) -> Result<()> {
    282     reader.seek(SeekFrom::Current(size as i64))?;
    283     Ok(())
    284 }
    285 
    286 fn skip_bytes_to<R: Read + Seek + ?Sized>(reader: &mut R, pos: u64) -> Result<u64> {
    287     let pos = reader.seek(SeekFrom::Start(pos))?;
    288     Ok(pos)
    289 }
    290 
    291 fn write_c2pa_box<W: Write>(
    292     w: &mut W,
    293     data: &[u8],
    294     is_manifest: bool,
    295     merkle_data: &[u8],
    296 ) -> Result<()> {
    297     let purpose_size = if is_manifest {
    298         MANIFEST.len() + 1
    299     } else {
    300         MERKLE.len() + 1
    301     };
    302     let merkle_size = if is_manifest { 8 } else { merkle_data.len() };
    303     let size = 8 + 16 + 4 + purpose_size + merkle_size + data.len(); // header + UUID + version/flags + data + zero terminated purpose + merkle data
    304     let bh = BoxHeaderLite::new(BoxType::UuidBox, size as u64, "uuid");
    305 
    306     // write out header
    307     bh.write(w)?;
    308 
    309     // write out c2pa extension UUID
    310     write_box_uuid_extension(w, &C2PA_UUID)?;
    311 
    312     // write out version and flags
    313     let version: u8 = 0;
    314     let flags: u32 = 0;
    315     write_box_header_ext(w, version, flags)?;
    316 
    317     // write purpose
    318     if is_manifest {
    319         w.write_all(MANIFEST.as_bytes())?;
    320         w.write_u8(0)?;
    321 
    322         // write no merkle flag
    323         w.write_u64::<BigEndian>(0)?;
    324     } else {
    325         w.write_all(MERKLE.as_bytes())?;
    326         w.write_u8(0)?;
    327 
    328         // write merkle cbor
    329         w.write_all(merkle_data)?;
    330     }
    331 
    332     // write out data
    333     w.write_all(data)?;
    334 
    335     Ok(())
    336 }
    337 
    338 fn write_xmp_box<W: Write>(w: &mut W, data: &[u8]) -> Result<()> {
    339     let size = 8 + 16 + 4 + data.len(); // header + UUID + data
    340     let bh = BoxHeaderLite::new(BoxType::UuidBox, size as u64, "uuid");
    341 
    342     // write out header
    343     bh.write(w)?;
    344 
    345     // write out XMP extension UUID
    346     write_box_uuid_extension(w, &XMP_UUID)?;
    347 
    348     // write out data
    349     w.write_all(data)?;
    350 
    351     Ok(())
    352 }
    353 
    354 fn _write_free_box<W: Write>(w: &mut W, size: usize) -> Result<()> {
    355     if size < 8 {
    356         return Err(Error::BadParam("cannot adjust free space".to_string()));
    357     }
    358 
    359     let zeros = vec![0u8; size - 8];
    360     let bh = BoxHeaderLite::new(BoxType::FreeBox, size as u64, "free");
    361 
    362     // write out header
    363     bh.write(w)?;
    364 
    365     // write out header
    366     w.write_all(&zeros)?;
    367 
    368     Ok(())
    369 }
    370 
    371 fn add_token_to_cache(bmff_path_map: &mut HashMap<String, Vec<Token>>, path: String, token: Token) {
    372     if let Some(token_list) = bmff_path_map.get_mut(&path) {
    373         token_list.push(token);
    374     } else {
    375         let token_list = vec![token];
    376         bmff_path_map.insert(path, token_list);
    377     }
    378 }
    379 
    380 fn path_from_token(bmff_tree: &Arena<BoxInfo>, current_node_token: &Token) -> Result<String> {
    381     let ancestors = current_node_token.ancestors(bmff_tree);
    382     let mut path = bmff_tree[*current_node_token].data.path.clone();
    383 
    384     for parent in ancestors {
    385         path = format!("{}/{}", parent.data.path, path);
    386     }
    387 
    388     if path.is_empty() {
    389         path = "/".to_string();
    390     }
    391 
    392     Ok(path)
    393 }
    394 
    395 fn get_top_level_box_offsets(
    396     bmff_tree: &Arena<BoxInfo>,
    397     bmff_path_map: &HashMap<String, Vec<Token>>,
    398 ) -> Vec<u64> {
    399     let mut tl_offsets = Vec::new();
    400 
    401     for (p, t) in bmff_path_map {
    402         // look for top level offsets
    403         if p.matches('/').count() == 1 {
    404             for token in t {
    405                 if let Some(box_info) = bmff_tree.get(*token) {
    406                     tl_offsets.push(box_info.data.offset);
    407                 }
    408             }
    409         }
    410     }
    411 
    412     tl_offsets
    413 }
    414 
    415 fn get_top_level_boxes(
    416     bmff_tree: &Arena<BoxInfo>,
    417     bmff_path_map: &HashMap<String, Vec<Token>>,
    418 ) -> Vec<BoxInfoLite> {
    419     let mut tl_boxes = Vec::new();
    420 
    421     for (p, t) in bmff_path_map {
    422         // look for top level offsets
    423         if p.matches('/').count() == 1 {
    424             for token in t {
    425                 if let Some(box_info) = bmff_tree.get(*token) {
    426                     tl_boxes.push(BoxInfoLite {
    427                         path: box_info.data.path.clone(),
    428                         offset: box_info.data.offset,
    429                         size: box_info.data.size,
    430                     });
    431                 }
    432             }
    433         }
    434     }
    435 
    436     tl_boxes
    437 }
    438 
    439 pub fn bmff_to_jumbf_exclusions(
    440     reader: &mut dyn CAIRead,
    441     bmff_exclusions: &[ExclusionsMap],
    442     bmff_v2: bool,
    443 ) -> Result<Vec<HashRange>> {
    444     let size = reader.seek(SeekFrom::End(0))?;
    445     reader.rewind()?;
    446 
    447     // create root node
    448     let root_box = BoxInfo {
    449         path: "".to_string(),
    450         offset: 0,
    451         size,
    452         box_type: BoxType::Empty,
    453         parent: None,
    454         user_type: None,
    455         version: None,
    456         flags: None,
    457     };
    458 
    459     let (mut bmff_tree, root_token) = Arena::with_data(root_box);
    460     let mut bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
    461 
    462     // build layout of the BMFF structure
    463     build_bmff_tree(reader, size, &mut bmff_tree, &root_token, &mut bmff_map)?;
    464 
    465     // get top level box offsets
    466     let mut tl_offsets = get_top_level_box_offsets(&bmff_tree, &bmff_map);
    467     tl_offsets.sort();
    468 
    469     let mut exclusions = Vec::new();
    470 
    471     for bmff_exclusion in bmff_exclusions {
    472         if let Some(box_token_list) = bmff_map.get(&bmff_exclusion.xpath) {
    473             for box_token in box_token_list {
    474                 let box_info = &bmff_tree[*box_token].data;
    475 
    476                 let box_start = box_info.offset;
    477                 let box_length = box_info.size;
    478 
    479                 let exclusion_start = box_start;
    480                 let exclusion_length = box_length;
    481 
    482                 // adjust exclusion bounds as needed
    483 
    484                 // check the length
    485                 if let Some(desired_length) = bmff_exclusion.length {
    486                     if desired_length as u64 != box_length {
    487                         continue;
    488                     }
    489                 }
    490 
    491                 // check the version
    492                 if let Some(desired_version) = bmff_exclusion.version {
    493                     if let Some(box_version) = box_info.version {
    494                         if desired_version != box_version {
    495                             continue;
    496                         }
    497                     }
    498                 }
    499 
    500                 // check the flags
    501                 if let Some(desired_flag_bytes) = &bmff_exclusion.flags {
    502                     let mut temp_bytes = [0u8; 4];
    503                     if desired_flag_bytes.len() >= 3 {
    504                         temp_bytes[0] = desired_flag_bytes[0];
    505                         temp_bytes[1] = desired_flag_bytes[1];
    506                         temp_bytes[2] = desired_flag_bytes[2];
    507                     }
    508                     let desired_flags = u32::from_be_bytes(temp_bytes);
    509 
    510                     if let Some(box_flags) = box_info.flags {
    511                         let exact = if let Some(is_exact) = bmff_exclusion.exact {
    512                             is_exact
    513                         } else {
    514                             true
    515                         };
    516 
    517                         if exact {
    518                             if desired_flags != box_flags {
    519                                 continue;
    520                             }
    521                         } else {
    522                             // bitwise match
    523                             if (desired_flags | box_flags) != desired_flags {
    524                                 continue;
    525                             }
    526                         }
    527                     }
    528                 }
    529 
    530                 // check data match
    531                 if let Some(data_map_vec) = &bmff_exclusion.data {
    532                     let mut should_add = true;
    533 
    534                     for data_map in data_map_vec {
    535                         // move to the start of exclusion
    536                         skip_bytes_to(reader, box_start + data_map.offset as u64)?;
    537 
    538                         // match the data
    539                         let mut buf = vec![0u8; data_map.value.len()];
    540                         reader.read_exact(&mut buf)?;
    541 
    542                         // does not match so skip
    543                         if !vec_compare(&data_map.value, &buf) {
    544                             should_add = false;
    545                             break;
    546                         }
    547                     }
    548                     if !should_add {
    549                         continue;
    550                     }
    551                 }
    552 
    553                 // reduce range if desired
    554                 if let Some(subset_vec) = &bmff_exclusion.subset {
    555                     for subset in subset_vec {
    556                         let exclusion = HashRange::new(
    557                             (exclusion_start + subset.offset as u64) as usize,
    558                             (if subset.length == 0 {
    559                                 exclusion_length - subset.offset as u64
    560                             } else {
    561                                 min(subset.length as u64, exclusion_length)
    562                             }) as usize,
    563                         );
    564 
    565                         exclusions.push(exclusion);
    566                     }
    567                 } else {
    568                     // exclude box in its entirty
    569                     let exclusion =
    570                         HashRange::new(exclusion_start as usize, exclusion_length as usize);
    571 
    572                     exclusions.push(exclusion);
    573 
    574                     // for BMFF V2 hashes we do not add hash offsets for top level boxes
    575                     // that are completely excluded, so remove from BMFF V2 hash offset calc
    576                     if let Some(pos) = tl_offsets.iter().position(|x| *x == exclusion_start) {
    577                         tl_offsets.remove(pos);
    578                     }
    579                 }
    580             }
    581         }
    582     }
    583 
    584     // add remaining top level offsets to be included when generating BMFF V2 hashes
    585     // note: this is technically not an exclusion but a replacement with a new range of bytes to be hashed
    586     if bmff_v2 {
    587         for tl_start in tl_offsets {
    588             let mut exclusion = HashRange::new(tl_start as usize, 1);
    589             exclusion.set_bmff_offset(tl_start);
    590 
    591             exclusions.push(exclusion);
    592         }
    593     }
    594 
    595     Ok(exclusions)
    596 }
    597 
    598 // `iloc`, `stco` and `co64` elements contain absolute file offsets so they need to be adjusted based on whether content was added or removed.
    599 // todo: when fragment support is added adjust these (/moof/iloc, /moof/mfro, /moof/traf/saio, /sidx)
    600 fn adjust_known_offsets<W: Write + CAIRead + ?Sized>(
    601     output: &mut W,
    602     bmff_tree: &Arena<BoxInfo>,
    603     bmff_path_map: &HashMap<String, Vec<Token>>,
    604     adjust: i32,
    605 ) -> Result<()> {
    606     let start_pos = output.stream_position()?; // save starting point
    607 
    608     // handle 32 bit offsets
    609     if let Some(stco_list) = bmff_path_map.get("/moov/trak/mdia/minf/stbl/stco") {
    610         for stco_token in stco_list {
    611             let stco_box_info = &bmff_tree[*stco_token].data;
    612             if stco_box_info.box_type != BoxType::StcoBox {
    613                 return Err(Error::InvalidAsset("Bad BMFF".to_string()));
    614             }
    615 
    616             // read stco box and patch
    617             output.seek(SeekFrom::Start(stco_box_info.offset))?;
    618 
    619             // read header
    620             let header = BoxHeaderLite::read(output)
    621                 .map_err(|_err| Error::InvalidAsset("Bad BMFF".to_string()))?;
    622             if header.name != BoxType::StcoBox {
    623                 return Err(Error::InvalidAsset("Bad BMFF".to_string()));
    624             }
    625 
    626             // read extended header
    627             let (_version, _flags) = read_box_header_ext(output)?; // box extensions
    628 
    629             // get count of offsets
    630             let entry_count = output.read_u32::<BigEndian>()?;
    631 
    632             // read and patch offsets
    633             let entry_start_pos = output.stream_position()?;
    634             let mut entries: Vec<u32> = Vec::new();
    635             for _e in 0..entry_count {
    636                 let offset = output.read_u32::<BigEndian>()?;
    637                 let new_offset = if adjust < 0 {
    638                     offset
    639                         - u32::try_from(adjust.abs()).map_err(|_| {
    640                             Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    641                         })?
    642                 } else {
    643                     offset
    644                         + u32::try_from(adjust).map_err(|_| {
    645                             Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    646                         })?
    647                 };
    648                 entries.push(new_offset);
    649             }
    650 
    651             // write updated offsets
    652             output.seek(SeekFrom::Start(entry_start_pos))?;
    653             for e in entries {
    654                 output.write_u32::<BigEndian>(e)?;
    655             }
    656         }
    657     }
    658 
    659     // handle 64 offsets
    660     if let Some(co64_list) = bmff_path_map.get("/moov/trak/mdia/minf/stbl/co64") {
    661         for co64_token in co64_list {
    662             let co64_box_info = &bmff_tree[*co64_token].data;
    663             if co64_box_info.box_type != BoxType::Co64Box {
    664                 return Err(Error::InvalidAsset("Bad BMFF".to_string()));
    665             }
    666 
    667             // read co64 box and patch
    668             output.seek(SeekFrom::Start(co64_box_info.offset))?;
    669 
    670             // read header
    671             let header = BoxHeaderLite::read(output)
    672                 .map_err(|_err| Error::InvalidAsset("Bad BMFF".to_string()))?;
    673             if header.name != BoxType::Co64Box {
    674                 return Err(Error::InvalidAsset("Bad BMFF".to_string()));
    675             }
    676 
    677             // read extended header
    678             let (_version, _flags) = read_box_header_ext(output)?; // box extensions
    679 
    680             // get count of offsets
    681             let entry_count = output.read_u32::<BigEndian>()?;
    682 
    683             // read and patch offsets
    684             let entry_start_pos = output.stream_position()?;
    685             let mut entries: Vec<u64> = Vec::new();
    686             for _e in 0..entry_count {
    687                 let offset = output.read_u64::<BigEndian>()?;
    688                 let new_offset = if adjust < 0 {
    689                     offset
    690                         - u64::try_from(adjust.abs()).map_err(|_| {
    691                             Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    692                         })?
    693                 } else {
    694                     offset
    695                         + u64::try_from(adjust).map_err(|_| {
    696                             Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    697                         })?
    698                 };
    699                 entries.push(new_offset);
    700             }
    701 
    702             // write updated offsets
    703             output.seek(SeekFrom::Start(entry_start_pos))?;
    704             for e in entries {
    705                 output.write_u64::<BigEndian>(e)?;
    706             }
    707         }
    708     }
    709 
    710     // handle meta iloc
    711     if let Some(iloc_list) = bmff_path_map.get("/meta/iloc") {
    712         for iloc_token in iloc_list {
    713             let iloc_box_info = &bmff_tree[*iloc_token].data;
    714             if iloc_box_info.box_type != BoxType::IlocBox {
    715                 return Err(Error::InvalidAsset("Bad BMFF".to_string()));
    716             }
    717 
    718             // read iloc box and patch
    719             output.seek(SeekFrom::Start(iloc_box_info.offset))?;
    720 
    721             // read header
    722             let header = BoxHeaderLite::read(output)
    723                 .map_err(|_err| Error::InvalidAsset("Bad BMFF".to_string()))?;
    724             if header.name != BoxType::IlocBox {
    725                 return Err(Error::InvalidAsset("Bad BMFF".to_string()));
    726             }
    727 
    728             // read extended header
    729             let (version, _flags) = read_box_header_ext(output)?; // box extensions
    730 
    731             // read next 16 bits (in file byte order)
    732             let mut iloc_header = [0u8, 2];
    733             output.read_exact(&mut iloc_header)?;
    734 
    735             // get offset size (high nibble)
    736             let offset_size: u8 = (iloc_header[0] & 0xf0) >> 4;
    737 
    738             // get length size (low nibble)
    739             let length_size: u8 = iloc_header[0] & 0x0f;
    740 
    741             // get box offset size (high nibble)
    742             let base_offset_size: u8 = (iloc_header[1] & 0xf0) >> 4;
    743 
    744             // get index size (low nibble)
    745             let index_size: u8 = iloc_header[1] & 0x0f;
    746 
    747             // get item count
    748             let item_count = match version {
    749                 _v if version < 2 => output.read_u16::<BigEndian>()? as u32,
    750                 _v if version == 2 => output.read_u32::<BigEndian>()?,
    751                 _ => {
    752                     return Err(Error::InvalidAsset(
    753                         "Bad BMFF unknown iloc format".to_string(),
    754                     ))
    755                 }
    756             };
    757 
    758             // walk the iloc items and patch
    759             for _i in 0..item_count {
    760                 // read item id
    761                 let _item_id = match version {
    762                     _v if version < 2 => output.read_u16::<BigEndian>()? as u32,
    763                     2 => output.read_u32::<BigEndian>()?,
    764                     _ => {
    765                         return Err(Error::InvalidAsset(
    766                             "Bad BMFF: unknown iloc item".to_string(),
    767                         ))
    768                     }
    769                 };
    770 
    771                 // read construction method
    772                 let construction_method = if version == 1 || version == 2 {
    773                     let mut cm_bytes = [0u8, 2];
    774                     output.read_exact(&mut cm_bytes)?;
    775 
    776                     // lower nibble of 2nd byte
    777                     cm_bytes[1] & 0x0f
    778                 } else {
    779                     0
    780                 };
    781 
    782                 // read data reference index
    783                 let _data_reference_index = output.read_u16::<BigEndian>()?;
    784 
    785                 let base_offset_file_pos = output.stream_position()?;
    786                 let base_offset = match base_offset_size {
    787                     0 => 0_u64,
    788                     4 => output.read_u32::<BigEndian>()? as u64,
    789                     8 => output.read_u64::<BigEndian>()?,
    790                     _ => {
    791                         return Err(Error::InvalidAsset(
    792                             "Bad BMFF: unknown iloc offset size".to_string(),
    793                         ))
    794                     }
    795                 };
    796 
    797                 // patch the offsets if needed
    798                 if construction_method == 0 {
    799                     // file offset construction method
    800                     if base_offset_size == 4 {
    801                         let new_offset = if adjust < 0 {
    802                             u32::try_from(base_offset).map_err(|_| {
    803                                 Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    804                             })? - u32::try_from(adjust.abs()).map_err(|_| {
    805                                 Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    806                             })?
    807                         } else {
    808                             u32::try_from(base_offset).map_err(|_| {
    809                                 Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    810                             })? + u32::try_from(adjust).map_err(|_| {
    811                                 Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    812                             })?
    813                         };
    814 
    815                         output.seek(SeekFrom::Start(base_offset_file_pos))?;
    816                         output.write_u32::<BigEndian>(new_offset)?;
    817                     }
    818 
    819                     if base_offset_size == 8 {
    820                         let new_offset = if adjust < 0 {
    821                             base_offset
    822                                 - u64::try_from(adjust.abs()).map_err(|_| {
    823                                     Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    824                                 })?
    825                         } else {
    826                             base_offset
    827                                 + u64::try_from(adjust).map_err(|_| {
    828                                     Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
    829                                 })?
    830                         };
    831 
    832                         output.seek(SeekFrom::Start(base_offset_file_pos))?;
    833                         output.write_u64::<BigEndian>(new_offset)?;
    834                     }
    835                 }
    836 
    837                 // read extent count
    838                 let extent_count = output.read_u16::<BigEndian>()?;
    839 
    840                 // consume the extents
    841                 for _e in 0..extent_count {
    842                     let _extent_index = if version == 1 || (version == 2 && index_size > 0) {
    843                         match base_offset_size {
    844                             4 => Some(output.read_u32::<BigEndian>()? as u64),
    845                             8 => Some(output.read_u64::<BigEndian>()?),
    846                             _ => None,
    847                         }
    848                     } else {
    849                         None
    850                     };
    851 
    852                     let extent_offset_file_pos = output.stream_position()?;
    853                     let extent_offset = match offset_size {
    854                         0 => 0_u64,
    855                         4 => output.read_u32::<BigEndian>()? as u64,
    856                         8 => output.read_u64::<BigEndian>()?,
    857                         _ => {
    858                             return Err(Error::InvalidAsset(
    859                                 "Bad BMFF: unknown iloc extent_offset size".to_string(),
    860                             ))
    861                         }
    862                     };
    863 
    864                     // no base offset so just adjust the raw extent_offset value
    865                     if construction_method == 0 && base_offset == 0 && extent_offset != 0 {
    866                         output.seek(SeekFrom::Start(extent_offset_file_pos))?;
    867                         match offset_size {
    868                             4 => {
    869                                 let new_offset = if adjust < 0 {
    870                                     extent_offset as u32
    871                                         - u32::try_from(adjust.abs()).map_err(|_| {
    872                                             Error::InvalidAsset(
    873                                                 "Bad BMFF offset adjustment".to_string(),
    874                                             )
    875                                         })?
    876                                 } else {
    877                                     extent_offset as u32
    878                                         + u32::try_from(adjust).map_err(|_| {
    879                                             Error::InvalidAsset(
    880                                                 "Bad BMFF offset adjustment".to_string(),
    881                                             )
    882                                         })?
    883                                 };
    884                                 output.write_u32::<BigEndian>(new_offset)?;
    885                             }
    886                             8 => {
    887                                 let new_offset = if adjust < 0 {
    888                                     extent_offset
    889                                         - u64::try_from(adjust.abs()).map_err(|_| {
    890                                             Error::InvalidAsset(
    891                                                 "Bad BMFF offset adjustment".to_string(),
    892                                             )
    893                                         })?
    894                                 } else {
    895                                     extent_offset
    896                                         + u64::try_from(adjust).map_err(|_| {
    897                                             Error::InvalidAsset(
    898                                                 "Bad BMFF offset adjustment".to_string(),
    899                                             )
    900                                         })?
    901                                 };
    902                                 output.write_u64::<BigEndian>(new_offset)?;
    903                             }
    904                             _ => {
    905                                 return Err(Error::InvalidAsset(
    906                                     "Bad BMFF: unknown extent_offset format".to_string(),
    907                                 ))
    908                             }
    909                         }
    910                     }
    911 
    912                     let _extent_length = match length_size {
    913                         0 => 0_u64,
    914                         4 => output.read_u32::<BigEndian>()? as u64,
    915                         8 => output.read_u64::<BigEndian>()?,
    916                         _ => {
    917                             return Err(Error::InvalidAsset(
    918                                 "Bad BMFF: unknown iloc offset size".to_string(),
    919                             ))
    920                         }
    921                     };
    922                 }
    923             }
    924         }
    925     }
    926 
    927     // restore seek point
    928     output.seek(SeekFrom::Start(start_pos))?;
    929     output.flush()?;
    930 
    931     Ok(())
    932 }
    933 
    934 pub(crate) fn build_bmff_tree<R: Read + Seek + ?Sized>(
    935     reader: &mut R,
    936     end: u64,
    937     bmff_tree: &mut Arena<BoxInfo>,
    938     current_node: &Token,
    939     bmff_path_map: &mut HashMap<String, Vec<Token>>,
    940 ) -> Result<()> {
    941     let start = reader.stream_position()?;
    942 
    943     let mut current = start;
    944     while current < end {
    945         // Get box header.
    946         let header = BoxHeaderLite::read(reader)
    947             .map_err(|_err| Error::InvalidAsset("Bad BMFF".to_string()))?;
    948 
    949         // Break if size zero BoxHeader
    950         let s = header.size;
    951         if s == 0 {
    952             break;
    953         }
    954 
    955         // Match and parse the supported atom boxes.
    956         match header.name {
    957             BoxType::UuidBox => {
    958                 let start = box_start(reader, header.large_size)?;
    959 
    960                 let mut extended_type = [0u8; 16]; // 16 bytes of UUID
    961                 reader.read_exact(&mut extended_type)?;
    962 
    963                 let (version, flags) = read_box_header_ext(reader)?;
    964 
    965                 let b = BoxInfo {
    966                     path: header.fourcc.clone(),
    967                     offset: start,
    968                     size: s,
    969                     box_type: BoxType::UuidBox,
    970                     parent: Some(*current_node),
    971                     user_type: Some(extended_type.to_vec()),
    972                     version: Some(version),
    973                     flags: Some(flags),
    974                 };
    975 
    976                 let new_token = current_node.append(bmff_tree, b);
    977 
    978                 let path = path_from_token(bmff_tree, &new_token)?;
    979                 add_token_to_cache(bmff_path_map, path, new_token);
    980 
    981                 // position seek pointer
    982                 skip_bytes_to(reader, start + s)?;
    983             }
    984             // container box types
    985             BoxType::MoovBox
    986             | BoxType::TrakBox
    987             | BoxType::MdiaBox
    988             | BoxType::MinfBox
    989             | BoxType::StblBox
    990             | BoxType::MoofBox
    991             | BoxType::TrafBox
    992             | BoxType::EdtsBox
    993             | BoxType::UdtaBox
    994             | BoxType::DinfBox
    995             | BoxType::TrefBox
    996             | BoxType::TregBox
    997             | BoxType::MvexBox
    998             | BoxType::MfraBox
    999             | BoxType::MetaBox
   1000             | BoxType::SchiBox => {
   1001                 let start = box_start(reader, header.large_size)?;
   1002 
   1003                 let b = if FULL_BOX_TYPES.contains(&header.fourcc.as_str()) {
   1004                     let (version, flags) = read_box_header_ext(reader)?; // box extensions
   1005                     BoxInfo {
   1006                         path: header.fourcc.clone(),
   1007                         offset: start,
   1008                         size: s,
   1009                         box_type: header.name,
   1010                         parent: Some(*current_node),
   1011                         user_type: None,
   1012                         version: Some(version),
   1013                         flags: Some(flags),
   1014                     }
   1015                 } else {
   1016                     BoxInfo {
   1017                         path: header.fourcc.clone(),
   1018                         offset: start,
   1019                         size: s,
   1020                         box_type: header.name,
   1021                         parent: Some(*current_node),
   1022                         user_type: None,
   1023                         version: None,
   1024                         flags: None,
   1025                     }
   1026                 };
   1027 
   1028                 let new_token = bmff_tree.new_node(b);
   1029                 current_node
   1030                     .append_node(bmff_tree, new_token)
   1031                     .map_err(|_err| Error::InvalidAsset("Bad BMFF Graph".to_string()))?;
   1032 
   1033                 let path = path_from_token(bmff_tree, &new_token)?;
   1034                 add_token_to_cache(bmff_path_map, path, new_token);
   1035 
   1036                 // consume all sub-boxes
   1037                 let mut current = reader.stream_position()?;
   1038                 let end = start + s;
   1039                 while current < end {
   1040                     build_bmff_tree(reader, end, bmff_tree, &new_token, bmff_path_map)?;
   1041                     current = reader.stream_position()?;
   1042                 }
   1043 
   1044                 // position seek pointer
   1045                 skip_bytes_to(reader, start + s)?;
   1046             }
   1047             _ => {
   1048                 let start = box_start(reader, header.large_size)?;
   1049 
   1050                 let b = if FULL_BOX_TYPES.contains(&header.fourcc.as_str()) {
   1051                     let (version, flags) = read_box_header_ext(reader)?; // box extensions
   1052                     BoxInfo {
   1053                         path: header.fourcc.clone(),
   1054                         offset: start,
   1055                         size: s,
   1056                         box_type: header.name,
   1057                         parent: Some(*current_node),
   1058                         user_type: None,
   1059                         version: Some(version),
   1060                         flags: Some(flags),
   1061                     }
   1062                 } else {
   1063                     BoxInfo {
   1064                         path: header.fourcc.clone(),
   1065                         offset: start,
   1066                         size: s,
   1067                         box_type: header.name,
   1068                         parent: Some(*current_node),
   1069                         user_type: None,
   1070                         version: None,
   1071                         flags: None,
   1072                     }
   1073                 };
   1074 
   1075                 let new_token = current_node.append(bmff_tree, b);
   1076 
   1077                 let path = path_from_token(bmff_tree, &new_token)?;
   1078                 add_token_to_cache(bmff_path_map, path, new_token);
   1079 
   1080                 // position seek pointer
   1081                 skip_bytes_to(reader, start + s)?;
   1082             }
   1083         }
   1084         current = reader.stream_position()?;
   1085     }
   1086 
   1087     Ok(())
   1088 }
   1089 
   1090 fn get_uuid_token(
   1091     bmff_tree: &Arena<BoxInfo>,
   1092     bmff_map: &HashMap<String, Vec<Token>>,
   1093     uuid: &[u8; 16],
   1094 ) -> Option<Token> {
   1095     if let Some(uuid_list) = bmff_map.get("/uuid") {
   1096         for uuid_token in uuid_list {
   1097             let box_info = &bmff_tree[*uuid_token];
   1098 
   1099             // make sure it is UUID box
   1100             if box_info.data.box_type == BoxType::UuidBox {
   1101                 if let Some(found_uuid) = &box_info.data.user_type {
   1102                     // make sure uuids match
   1103                     if vec_compare(uuid, found_uuid) {
   1104                         return Some(*uuid_token);
   1105                     }
   1106                 }
   1107             }
   1108         }
   1109     }
   1110     None
   1111 }
   1112 
   1113 pub(crate) struct C2PABmffBoxes {
   1114     pub manifest_bytes: Option<Vec<u8>>,
   1115     pub bmff_merkle: Vec<BmffMerkleMap>,
   1116     pub box_infos: Vec<BoxInfoLite>,
   1117     pub xmp: Option<String>,
   1118 }
   1119 
   1120 pub(crate) fn read_bmff_c2pa_boxes(reader: &mut dyn CAIRead) -> Result<C2PABmffBoxes> {
   1121     let size = reader.seek(SeekFrom::End(0))?;
   1122     reader.rewind()?;
   1123 
   1124     // create root node
   1125     let root_box = BoxInfo {
   1126         path: "".to_string(),
   1127         offset: 0,
   1128         size,
   1129         box_type: BoxType::Empty,
   1130         parent: None,
   1131         user_type: None,
   1132         version: None,
   1133         flags: None,
   1134     };
   1135 
   1136     let (mut bmff_tree, root_token) = Arena::with_data(root_box);
   1137     let mut bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
   1138 
   1139     // build layout of the BMFF structure
   1140     build_bmff_tree(reader, size, &mut bmff_tree, &root_token, &mut bmff_map)?;
   1141 
   1142     let mut output: Option<Vec<u8>> = None;
   1143     let mut xmp: Option<String> = None;
   1144     let mut _first_aux_uuid = 0;
   1145     let mut merkle_boxes: Vec<BmffMerkleMap> = Vec::new();
   1146 
   1147     // grab top level (for now) C2PA box
   1148     if let Some(uuid_list) = bmff_map.get("/uuid") {
   1149         let mut manifest_store_cnt = 0;
   1150 
   1151         for uuid_token in uuid_list {
   1152             let box_info = &bmff_tree[*uuid_token];
   1153 
   1154             // make sure it is UUID box
   1155             if box_info.data.box_type == BoxType::UuidBox {
   1156                 if let Some(uuid) = &box_info.data.user_type {
   1157                     // make sure it is a C2PA ContentProvenanceBox box
   1158                     if vec_compare(&C2PA_UUID, uuid) {
   1159                         let mut data_len = box_info.data.size - HEADER_SIZE - 16 /*UUID*/;
   1160 
   1161                         // set reader to start of box contents
   1162                         skip_bytes_to(reader, box_info.data.offset + HEADER_SIZE + 16)?;
   1163 
   1164                         // Fullbox => 8 bits for version 24 bits for flags
   1165                         let (_version, _flags) = read_box_header_ext(reader)?;
   1166                         data_len -= 4;
   1167 
   1168                         // get the purpose
   1169                         let mut purpose = Vec::with_capacity(64);
   1170                         loop {
   1171                             let mut buf = [0; 1];
   1172                             reader.read_exact(&mut buf)?;
   1173                             data_len -= 1;
   1174                             if buf[0] == 0x00 {
   1175                                 break;
   1176                             } else {
   1177                                 purpose.push(buf[0]);
   1178                             }
   1179                         }
   1180 
   1181                         // is the purpose manifest?
   1182                         if vec_compare(&purpose, MANIFEST.as_bytes()) {
   1183                             // offset to first aux uuid with purpose merkle
   1184                             let mut buf = [0u8; 8];
   1185                             reader.read_exact(&mut buf)?;
   1186                             data_len -= 8;
   1187 
   1188                             // offset to first aux uuid
   1189                             let offset = u64::from_be_bytes(buf);
   1190 
   1191                             // read the manifest
   1192                             if manifest_store_cnt == 0 {
   1193                                 let mut manifest = vec![0u8; data_len as usize];
   1194                                 reader.read_exact(&mut manifest)?;
   1195                                 output = Some(manifest);
   1196 
   1197                                 manifest_store_cnt += 1;
   1198                             } else {
   1199                                 return Err(Error::TooManyManifestStores);
   1200                             }
   1201 
   1202                             // if contains offset this asset contains additional UUID boxes
   1203                             if offset != 0 {
   1204                                 _first_aux_uuid = offset;
   1205                             }
   1206                         } else if vec_compare(&purpose, MERKLE.as_bytes()) {
   1207                             let mut merkle = vec![0u8; data_len as usize];
   1208                             reader.read_exact(&mut merkle)?;
   1209 
   1210                             // strip trailing zeros
   1211                             loop {
   1212                                 if !merkle.is_empty() && merkle[merkle.len() - 1] == 0 {
   1213                                     merkle.pop();
   1214                                 }
   1215 
   1216                                 if merkle.is_empty() || merkle[merkle.len() - 1] != 0 {
   1217                                     break;
   1218                                 }
   1219                             }
   1220 
   1221                             // find uuid from uuid list
   1222                             let mm: BmffMerkleMap = serde_cbor::from_slice(&merkle)?;
   1223                             merkle_boxes.push(mm);
   1224                         }
   1225                     } else if vec_compare(&XMP_UUID, uuid) {
   1226                         let data_len = box_info.data.size - HEADER_SIZE - 16 /*UUID*/;
   1227 
   1228                         // set reader to start of box contents
   1229                         skip_bytes_to(reader, box_info.data.offset + HEADER_SIZE + 16)?;
   1230 
   1231                         let mut xmp_vec = vec![0u8; data_len as usize];
   1232                         reader.read_exact(&mut xmp_vec)?;
   1233 
   1234                         if let Ok(xmp_string) = String::from_utf8(xmp_vec) {
   1235                             xmp = Some(xmp_string);
   1236                         }
   1237                     }
   1238                 }
   1239             }
   1240         }
   1241     }
   1242 
   1243     // get position ordered list of boxes
   1244     let mut box_infos: Vec<BoxInfoLite> = get_top_level_boxes(&bmff_tree, &bmff_map);
   1245     box_infos.sort_by(|a, b| a.offset.cmp(&b.offset));
   1246 
   1247     Ok(C2PABmffBoxes {
   1248         manifest_bytes: output,
   1249         bmff_merkle: merkle_boxes,
   1250         box_infos,
   1251         xmp,
   1252     })
   1253 }
   1254 
   1255 impl CAIReader for BmffIO {
   1256     fn read_cai(&self, reader: &mut dyn CAIRead) -> Result<Vec<u8>> {
   1257         let c2pa_boxes = read_bmff_c2pa_boxes(reader)?;
   1258 
   1259         c2pa_boxes.manifest_bytes.ok_or(Error::JumbfNotFound)
   1260     }
   1261 
   1262     // Get XMP block
   1263     fn read_xmp(&self, reader: &mut dyn CAIRead) -> Option<String> {
   1264         let c2pa_boxes = read_bmff_c2pa_boxes(reader).ok()?;
   1265 
   1266         c2pa_boxes.xmp
   1267     }
   1268 }
   1269 
   1270 impl AssetIO for BmffIO {
   1271     fn asset_patch_ref(&self) -> Option<&dyn AssetPatch> {
   1272         Some(self)
   1273     }
   1274 
   1275     fn read_cai_store(&self, asset_path: &Path) -> Result<Vec<u8>> {
   1276         let mut f = File::open(asset_path)?;
   1277         self.read_cai(&mut f)
   1278     }
   1279 
   1280     fn save_cai_store(&self, asset_path: &std::path::Path, store_bytes: &[u8]) -> Result<()> {
   1281         let mut input_stream = std::fs::OpenOptions::new()
   1282             .read(true)
   1283             .open(asset_path)
   1284             .map_err(Error::IoError)?;
   1285 
   1286         let mut temp_file = Builder::new()
   1287             .prefix("c2pa_temp")
   1288             .rand_bytes(5)
   1289             .tempfile()?;
   1290 
   1291         self.write_cai(&mut input_stream, &mut temp_file, store_bytes)?;
   1292 
   1293         // copy temp file to asset
   1294         rename_or_copy(temp_file, asset_path)
   1295     }
   1296 
   1297     fn get_object_locations(
   1298         &self,
   1299         _asset_path: &std::path::Path,
   1300     ) -> Result<Vec<HashObjectPositions>> {
   1301         let vec: Vec<HashObjectPositions> = Vec::new();
   1302         Ok(vec)
   1303     }
   1304 
   1305     fn remove_cai_store(&self, asset_path: &Path) -> Result<()> {
   1306         let mut input_file = std::fs::File::open(asset_path)?;
   1307 
   1308         let mut temp_file = Builder::new()
   1309             .prefix("c2pa_temp")
   1310             .rand_bytes(5)
   1311             .tempfile()?;
   1312 
   1313         self.remove_cai_store_from_stream(&mut input_file, &mut temp_file)?;
   1314 
   1315         // copy temp file to asset
   1316         rename_or_copy(temp_file, asset_path)
   1317     }
   1318 
   1319     fn new(asset_type: &str) -> Self
   1320     where
   1321         Self: Sized,
   1322     {
   1323         BmffIO {
   1324             bmff_format: asset_type.to_string(),
   1325         }
   1326     }
   1327 
   1328     fn get_handler(&self, asset_type: &str) -> Box<dyn AssetIO> {
   1329         Box::new(BmffIO::new(asset_type))
   1330     }
   1331 
   1332     fn get_reader(&self) -> &dyn CAIReader {
   1333         self
   1334     }
   1335 
   1336     fn get_writer(&self, asset_type: &str) -> Option<Box<dyn CAIWriter>> {
   1337         Some(Box::new(BmffIO::new(asset_type)))
   1338     }
   1339 
   1340     fn remote_ref_writer_ref(&self) -> Option<&dyn RemoteRefEmbed> {
   1341         Some(self)
   1342     }
   1343 
   1344     fn supported_types(&self) -> &[&str] {
   1345         &SUPPORTED_TYPES
   1346     }
   1347 }
   1348 
   1349 impl CAIWriter for BmffIO {
   1350     fn write_cai(
   1351         &self,
   1352         input_stream: &mut dyn CAIRead,
   1353         output_stream: &mut dyn CAIReadWrite,
   1354         store_bytes: &[u8],
   1355     ) -> Result<()> {
   1356         let size = input_stream.seek(SeekFrom::End(0))?;
   1357         input_stream.rewind()?;
   1358 
   1359         // create root node
   1360         let root_box = BoxInfo {
   1361             path: "".to_string(),
   1362             offset: 0,
   1363             size,
   1364             box_type: BoxType::Empty,
   1365             parent: None,
   1366             user_type: None,
   1367             version: None,
   1368             flags: None,
   1369         };
   1370 
   1371         let (mut bmff_tree, root_token) = Arena::with_data(root_box);
   1372         let mut bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
   1373 
   1374         // build layout of the BMFF structure
   1375         build_bmff_tree(
   1376             input_stream,
   1377             size,
   1378             &mut bmff_tree,
   1379             &root_token,
   1380             &mut bmff_map,
   1381         )?;
   1382 
   1383         // get ftyp location
   1384         // start after ftyp
   1385         let ftyp_token = bmff_map.get("/ftyp").ok_or(Error::UnsupportedType)?; // todo check ftyps to make sure we support any special format requirements
   1386         let ftyp_info = &bmff_tree[ftyp_token[0]].data;
   1387         let ftyp_offset = ftyp_info.offset;
   1388         let ftyp_size = ftyp_info.size;
   1389 
   1390         // get position to insert c2pa
   1391         let (c2pa_start, c2pa_length) =
   1392             if let Some(c2pa_token) = get_uuid_token(&bmff_tree, &bmff_map, &C2PA_UUID) {
   1393                 let uuid_info = &bmff_tree[c2pa_token].data;
   1394 
   1395                 (uuid_info.offset, Some(uuid_info.size))
   1396             } else {
   1397                 ((ftyp_offset + ftyp_size), None)
   1398             };
   1399 
   1400         let mut new_c2pa_box: Vec<u8> = Vec::with_capacity(store_bytes.len() * 2);
   1401         let merkle_data: &[u8] = &[]; // not yet supported
   1402         write_c2pa_box(&mut new_c2pa_box, store_bytes, true, merkle_data)?;
   1403         let new_c2pa_box_size = new_c2pa_box.len();
   1404 
   1405         let (start, end) = if let Some(c2pa_length) = c2pa_length {
   1406             let start = usize::value_from(c2pa_start)
   1407                 .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?; // get beginning of chunk which starts 4 bytes before label
   1408 
   1409             let end = usize::value_from(c2pa_start + c2pa_length)
   1410                 .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?;
   1411 
   1412             (start, end)
   1413         } else {
   1414             // insert new c2pa
   1415             let end = usize::value_from(c2pa_start)
   1416                 .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?;
   1417 
   1418             (end, end)
   1419         };
   1420 
   1421         // write content before ContentProvenanceBox
   1422         input_stream.rewind()?;
   1423         let mut before_manifest = input_stream.take(start as u64);
   1424         std::io::copy(&mut before_manifest, output_stream)?;
   1425 
   1426         // write ContentProvenanceBox
   1427         output_stream.write_all(&new_c2pa_box)?;
   1428 
   1429         // calc offset adjustments
   1430         let offset_adjust: i32 = if end == 0 {
   1431             new_c2pa_box_size as i32
   1432         } else {
   1433             // value could be negative if box is truncated
   1434             let existing_c2pa_box_size = end - start;
   1435             let pad_size: i32 = new_c2pa_box_size as i32 - existing_c2pa_box_size as i32;
   1436             pad_size
   1437         };
   1438 
   1439         // write content after ContentProvenanceBox
   1440         input_stream.seek(SeekFrom::Start(end as u64))?;
   1441         std::io::copy(input_stream, output_stream)?;
   1442 
   1443         // Manipulating the UUID box means we may need some patch offsets if they are file absolute offsets.
   1444 
   1445         // create root node
   1446         let root_box = BoxInfo {
   1447             path: "".to_string(),
   1448             offset: 0,
   1449             size,
   1450             box_type: BoxType::Empty,
   1451             parent: None,
   1452             user_type: None,
   1453             version: None,
   1454             flags: None,
   1455         };
   1456 
   1457         // map box layout of current output file
   1458         let (mut output_bmff_tree, root_token) = Arena::with_data(root_box);
   1459         let mut output_bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
   1460 
   1461         let size = output_stream.seek(SeekFrom::End(0))?;
   1462         output_stream.rewind()?;
   1463         build_bmff_tree(
   1464             output_stream,
   1465             size,
   1466             &mut output_bmff_tree,
   1467             &root_token,
   1468             &mut output_bmff_map,
   1469         )?;
   1470 
   1471         // adjust offsets based on current layout
   1472         output_stream.rewind()?;
   1473         adjust_known_offsets(
   1474             output_stream,
   1475             &output_bmff_tree,
   1476             &output_bmff_map,
   1477             offset_adjust,
   1478         )
   1479     }
   1480 
   1481     fn get_object_locations_from_stream(
   1482         &self,
   1483         _input_stream: &mut dyn CAIRead,
   1484     ) -> Result<Vec<HashObjectPositions>> {
   1485         let vec: Vec<HashObjectPositions> = Vec::new();
   1486         Ok(vec)
   1487     }
   1488 
   1489     fn remove_cai_store_from_stream(
   1490         &self,
   1491         input_stream: &mut dyn CAIRead,
   1492         output_stream: &mut dyn CAIReadWrite,
   1493     ) -> Result<()> {
   1494         let size = input_stream.seek(SeekFrom::End(0))?;
   1495         input_stream.rewind()?;
   1496 
   1497         // create root node
   1498         let root_box = BoxInfo {
   1499             path: "".to_string(),
   1500             offset: 0,
   1501             size,
   1502             box_type: BoxType::Empty,
   1503             parent: None,
   1504             user_type: None,
   1505             version: None,
   1506             flags: None,
   1507         };
   1508 
   1509         let (mut bmff_tree, root_token) = Arena::with_data(root_box);
   1510         let mut bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
   1511 
   1512         // build layout of the BMFF structure
   1513         build_bmff_tree(
   1514             input_stream,
   1515             size,
   1516             &mut bmff_tree,
   1517             &root_token,
   1518             &mut bmff_map,
   1519         )?;
   1520 
   1521         // get position of c2pa manifest
   1522         let (c2pa_start, c2pa_length) =
   1523             if let Some(c2pa_token) = get_uuid_token(&bmff_tree, &bmff_map, &C2PA_UUID) {
   1524                 let uuid_info = &bmff_tree[c2pa_token].data;
   1525 
   1526                 (uuid_info.offset, Some(uuid_info.size))
   1527             } else {
   1528                 input_stream.rewind()?;
   1529                 std::io::copy(input_stream, output_stream)?;
   1530                 return Ok(()); // no box to remove, propagate source to output
   1531             };
   1532 
   1533         let (start, end) = if let Some(c2pa_length) = c2pa_length {
   1534             let start = usize::value_from(c2pa_start)
   1535                 .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?; // get beginning of chunk which starts 4 bytes before label
   1536 
   1537             let end = usize::value_from(c2pa_start + c2pa_length)
   1538                 .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?;
   1539 
   1540             (start, end)
   1541         } else {
   1542             return Err(Error::InvalidAsset("value out of range".to_string()));
   1543         };
   1544 
   1545         // write content before ContentProvenanceBox
   1546         input_stream.rewind()?;
   1547         let mut before_manifest = input_stream.take(start as u64);
   1548         std::io::copy(&mut before_manifest, output_stream)?;
   1549 
   1550         // calc offset adjustments
   1551         // value will be negative since the box is truncated
   1552         let new_c2pa_box_size: i32 = 0;
   1553         let existing_c2pa_box_size = end - start;
   1554         let offset_adjust = new_c2pa_box_size - existing_c2pa_box_size as i32;
   1555 
   1556         // write content after ContentProvenanceBox
   1557         input_stream.seek(SeekFrom::Start(end as u64))?;
   1558         std::io::copy(input_stream, output_stream)?;
   1559 
   1560         // Manipulating the UUID box means we may need some patch offsets if they are file absolute offsets.
   1561 
   1562         // create root node
   1563         let root_box = BoxInfo {
   1564             path: "".to_string(),
   1565             offset: 0,
   1566             size,
   1567             box_type: BoxType::Empty,
   1568             parent: None,
   1569             user_type: None,
   1570             version: None,
   1571             flags: None,
   1572         };
   1573 
   1574         // map box layout of current output file
   1575         let (mut output_bmff_tree, root_token) = Arena::with_data(root_box);
   1576         let mut output_bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
   1577 
   1578         let size = output_stream.seek(SeekFrom::End(0))?;
   1579         output_stream.rewind()?;
   1580         build_bmff_tree(
   1581             output_stream,
   1582             size,
   1583             &mut output_bmff_tree,
   1584             &root_token,
   1585             &mut output_bmff_map,
   1586         )?;
   1587 
   1588         // adjust offsets based on current layout
   1589         output_stream.rewind()?;
   1590         adjust_known_offsets(
   1591             output_stream,
   1592             &output_bmff_tree,
   1593             &output_bmff_map,
   1594             offset_adjust,
   1595         )
   1596     }
   1597 }
   1598 
   1599 impl AssetPatch for BmffIO {
   1600     fn patch_cai_store(&self, asset_path: &std::path::Path, store_bytes: &[u8]) -> Result<()> {
   1601         let mut asset = OpenOptions::new()
   1602             .write(true)
   1603             .read(true)
   1604             .create(false)
   1605             .open(asset_path)?;
   1606         let size = asset.seek(SeekFrom::End(0))?;
   1607         asset.rewind()?;
   1608 
   1609         // create root node
   1610         let root_box = BoxInfo {
   1611             path: "".to_string(),
   1612             offset: 0,
   1613             size,
   1614             box_type: BoxType::Empty,
   1615             parent: None,
   1616             user_type: None,
   1617             version: None,
   1618             flags: None,
   1619         };
   1620 
   1621         let (mut bmff_tree, root_token) = Arena::with_data(root_box);
   1622         let mut bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
   1623 
   1624         // build layout of the BMFF structure
   1625         build_bmff_tree(&mut asset, size, &mut bmff_tree, &root_token, &mut bmff_map)?;
   1626 
   1627         // get position to insert c2pa
   1628         let (c2pa_start, c2pa_length) = if let Some(uuid_tokens) = bmff_map.get("/uuid") {
   1629             let uuid_info = &bmff_tree[uuid_tokens[0]].data;
   1630 
   1631             // is this a C2PA manifest
   1632             let is_c2pa = if let Some(uuid) = &uuid_info.user_type {
   1633                 // make sure it is a C2PA box
   1634                 vec_compare(&C2PA_UUID, uuid)
   1635             } else {
   1636                 false
   1637             };
   1638 
   1639             if is_c2pa {
   1640                 (uuid_info.offset, Some(uuid_info.size))
   1641             } else {
   1642                 (0, None)
   1643             }
   1644         } else {
   1645             return Err(Error::InvalidAsset(
   1646                 "patch_cai_store found no manifest store to patch.".to_string(),
   1647             ));
   1648         };
   1649 
   1650         if let Some(manifest_length) = c2pa_length {
   1651             let mut new_c2pa_box: Vec<u8> = Vec::with_capacity(store_bytes.len() * 2);
   1652             let merkle_data: &[u8] = &[]; // not yet supported
   1653             write_c2pa_box(&mut new_c2pa_box, store_bytes, true, merkle_data)?;
   1654             let new_c2pa_box_size = new_c2pa_box.len();
   1655 
   1656             if new_c2pa_box_size as u64 == manifest_length {
   1657                 asset.seek(SeekFrom::Start(c2pa_start))?;
   1658                 asset.write_all(&new_c2pa_box)?;
   1659                 Ok(())
   1660             } else {
   1661                 Err(Error::InvalidAsset(
   1662                     "patch_cai_store store size mismatch.".to_string(),
   1663                 ))
   1664             }
   1665         } else {
   1666             Err(Error::InvalidAsset(
   1667                 "patch_cai_store store size mismatch.".to_string(),
   1668             ))
   1669         }
   1670     }
   1671 }
   1672 
   1673 impl RemoteRefEmbed for BmffIO {
   1674     #[allow(unused_variables)]
   1675     fn embed_reference(
   1676         &self,
   1677         asset_path: &Path,
   1678         embed_ref: crate::asset_io::RemoteRefEmbedType,
   1679     ) -> Result<()> {
   1680         match embed_ref {
   1681             crate::asset_io::RemoteRefEmbedType::Xmp(manifest_uri) => {
   1682                 let output_buf = Vec::new();
   1683                 let mut output_stream = Cursor::new(output_buf);
   1684 
   1685                 // block so that source file is closed after embed
   1686                 {
   1687                     let mut source_stream = std::fs::File::open(asset_path)?;
   1688                     self.embed_reference_to_stream(
   1689                         &mut source_stream,
   1690                         &mut output_stream,
   1691                         RemoteRefEmbedType::Xmp(manifest_uri),
   1692                     )?;
   1693                 }
   1694 
   1695                 // write will replace exisiting contents
   1696                 std::fs::write(asset_path, output_stream.into_inner())?;
   1697                 Ok(())
   1698             }
   1699             crate::asset_io::RemoteRefEmbedType::StegoS(_) => Err(Error::UnsupportedType),
   1700             crate::asset_io::RemoteRefEmbedType::StegoB(_) => Err(Error::UnsupportedType),
   1701             crate::asset_io::RemoteRefEmbedType::Watermark(_) => Err(Error::UnsupportedType),
   1702         }
   1703     }
   1704 
   1705     fn embed_reference_to_stream(
   1706         &self,
   1707         input_stream: &mut dyn CAIRead,
   1708         output_stream: &mut dyn CAIReadWrite,
   1709         embed_ref: RemoteRefEmbedType,
   1710     ) -> Result<()> {
   1711         match embed_ref {
   1712             crate::asset_io::RemoteRefEmbedType::Xmp(manifest_uri) => {
   1713                 let xmp = match self.get_reader().read_xmp(input_stream) {
   1714                     Some(xmp) => add_provenance(&xmp, &manifest_uri)?,
   1715                     None => {
   1716                         let xmp = format!("http://ns.adobe.com/xap/1.0/\0 {}", MIN_XMP);
   1717                         add_provenance(&xmp, &manifest_uri)?
   1718                     }
   1719                 };
   1720 
   1721                 let size = input_stream.seek(SeekFrom::End(0))?;
   1722                 input_stream.rewind()?;
   1723 
   1724                 // create root node
   1725                 let root_box = BoxInfo {
   1726                     path: "".to_string(),
   1727                     offset: 0,
   1728                     size,
   1729                     box_type: BoxType::Empty,
   1730                     parent: None,
   1731                     user_type: None,
   1732                     version: None,
   1733                     flags: None,
   1734                 };
   1735 
   1736                 let (mut bmff_tree, root_token) = Arena::with_data(root_box);
   1737                 let mut bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
   1738 
   1739                 // build layout of the BMFF structure
   1740                 build_bmff_tree(
   1741                     input_stream,
   1742                     size,
   1743                     &mut bmff_tree,
   1744                     &root_token,
   1745                     &mut bmff_map,
   1746                 )?;
   1747 
   1748                 // get ftyp location
   1749                 // start after ftyp
   1750                 let ftyp_token = bmff_map.get("/ftyp").ok_or(Error::UnsupportedType)?; // todo check ftyps to make sure we support any special format requirements
   1751                 let ftyp_info = &bmff_tree[ftyp_token[0]].data;
   1752                 let ftyp_offset = ftyp_info.offset;
   1753                 let ftyp_size = ftyp_info.size;
   1754 
   1755                 // get position to insert xmp
   1756                 let (xmp_start, xmp_length) =
   1757                     if let Some(c2pa_token) = get_uuid_token(&bmff_tree, &bmff_map, &XMP_UUID) {
   1758                         let uuid_info = &bmff_tree[c2pa_token].data;
   1759 
   1760                         (uuid_info.offset, Some(uuid_info.size))
   1761                     } else {
   1762                         ((ftyp_offset + ftyp_size), None)
   1763                     };
   1764 
   1765                 let mut new_xmp_box: Vec<u8> = Vec::with_capacity(xmp.len() * 2);
   1766                 write_xmp_box(&mut new_xmp_box, xmp.as_bytes())?;
   1767                 let new_xmp_box_size = new_xmp_box.len();
   1768 
   1769                 let (start, end) = if let Some(xmp_length) = xmp_length {
   1770                     let start = usize::value_from(xmp_start)
   1771                         .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?; // get beginning of chunk which starts 4 bytes before label
   1772 
   1773                     let end = usize::value_from(xmp_start + xmp_length)
   1774                         .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?;
   1775 
   1776                     (start, end)
   1777                 } else {
   1778                     // insert new c2pa
   1779                     let end = usize::value_from(xmp_start)
   1780                         .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?;
   1781 
   1782                     (end, end)
   1783                 };
   1784 
   1785                 // write content before XMP box
   1786                 input_stream.rewind()?;
   1787                 let mut before_manifest = input_stream.take(start as u64);
   1788                 std::io::copy(&mut before_manifest, output_stream)?;
   1789 
   1790                 // write ContentProvenanceBox
   1791                 output_stream.write_all(&new_xmp_box)?;
   1792 
   1793                 // calc offset adjustments
   1794                 let offset_adjust: i32 = if end == 0 {
   1795                     new_xmp_box_size as i32
   1796                 } else {
   1797                     // value could be negative if box is truncated
   1798                     let existing_xmp_box_size = end - start;
   1799                     let pad_size: i32 = new_xmp_box_size as i32 - existing_xmp_box_size as i32;
   1800                     pad_size
   1801                 };
   1802 
   1803                 // write content after XMP box
   1804                 input_stream.seek(SeekFrom::Start(end as u64))?;
   1805                 std::io::copy(input_stream, output_stream)?;
   1806 
   1807                 // Manipulating the UUID box means we may need some patch offsets if they are file absolute offsets.
   1808 
   1809                 // create root node
   1810                 let root_box = BoxInfo {
   1811                     path: "".to_string(),
   1812                     offset: 0,
   1813                     size,
   1814                     box_type: BoxType::Empty,
   1815                     parent: None,
   1816                     user_type: None,
   1817                     version: None,
   1818                     flags: None,
   1819                 };
   1820 
   1821                 // map box layout of current output file
   1822                 let (mut output_bmff_tree, root_token) = Arena::with_data(root_box);
   1823                 let mut output_bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
   1824 
   1825                 let size = output_stream.seek(SeekFrom::End(0))?;
   1826                 output_stream.rewind()?;
   1827                 build_bmff_tree(
   1828                     output_stream,
   1829                     size,
   1830                     &mut output_bmff_tree,
   1831                     &root_token,
   1832                     &mut output_bmff_map,
   1833                 )?;
   1834 
   1835                 // adjust offsets based on current layout
   1836                 output_stream.rewind()?;
   1837                 adjust_known_offsets(
   1838                     output_stream,
   1839                     &output_bmff_tree,
   1840                     &output_bmff_map,
   1841                     offset_adjust,
   1842                 )
   1843             }
   1844             crate::asset_io::RemoteRefEmbedType::StegoS(_) => Err(Error::UnsupportedType),
   1845             crate::asset_io::RemoteRefEmbedType::StegoB(_) => Err(Error::UnsupportedType),
   1846             crate::asset_io::RemoteRefEmbedType::Watermark(_) => Err(Error::UnsupportedType),
   1847         }
   1848     }
   1849 }
   1850 #[cfg(test)]
   1851 pub mod tests {
   1852     #![allow(clippy::expect_used)]
   1853     #![allow(clippy::panic)]
   1854     #![allow(clippy::unwrap_used)]
   1855 
   1856     use tempfile::tempdir;
   1857 
   1858     use super::*;
   1859     use crate::utils::test::{fixture_path, temp_dir_path};
   1860 
   1861     #[cfg(not(target_arch = "wasm32"))]
   1862     #[cfg(feature = "file_io")]
   1863     #[test]
   1864     fn test_read_mp4() {
   1865         use crate::{
   1866             status_tracker::{report_split_errors, DetailedStatusTracker, StatusTracker},
   1867             store::Store,
   1868         };
   1869 
   1870         let ap = fixture_path("video1.mp4");
   1871 
   1872         let mut log = DetailedStatusTracker::default();
   1873         let store = Store::load_from_asset(&ap, true, &mut log);
   1874 
   1875         let errors = report_split_errors(log.get_log_mut());
   1876         assert!(errors.is_empty());
   1877 
   1878         if let Ok(s) = store {
   1879             print!("Store: \n{s}");
   1880         }
   1881     }
   1882 
   1883     #[test]
   1884     fn test_xmp_write() {
   1885         let data = "some test data";
   1886         let source = fixture_path("video1.mp4");
   1887 
   1888         let temp_dir = tempdir().unwrap();
   1889         let output = temp_dir_path(&temp_dir, "video1-out.mp4");
   1890 
   1891         std::fs::copy(source, &output).unwrap();
   1892 
   1893         let bmff = BmffIO::new("mp4");
   1894 
   1895         let eh = bmff.remote_ref_writer_ref().unwrap();
   1896 
   1897         eh.embed_reference(&output, RemoteRefEmbedType::Xmp(data.to_string()))
   1898             .unwrap();
   1899 
   1900         let mut output_stream = std::fs::File::open(&output).unwrap();
   1901         let xmp = bmff.get_reader().read_xmp(&mut output_stream).unwrap();
   1902 
   1903         let loaded = crate::utils::xmp_inmemory_utils::extract_provenance(&xmp).unwrap();
   1904 
   1905         assert_eq!(&loaded, data);
   1906     }
   1907 
   1908     #[test]
   1909     fn test_truncated_c2pa_write_mp4() {
   1910         let test_data = "some test data".as_bytes();
   1911         let source = fixture_path("video1.mp4");
   1912 
   1913         let mut success = false;
   1914         if let Ok(temp_dir) = tempdir() {
   1915             let output = temp_dir_path(&temp_dir, "mp4_test.mp4");
   1916 
   1917             if let Ok(_size) = std::fs::copy(source, &output) {
   1918                 let bmff = BmffIO::new("mp4");
   1919 
   1920                 //let test_data =  bmff.read_cai_store(&source).unwrap();
   1921                 if let Ok(()) = bmff.save_cai_store(&output, test_data) {
   1922                     if let Ok(read_test_data) = bmff.read_cai_store(&output) {
   1923                         assert!(vec_compare(test_data, &read_test_data));
   1924                         success = true;
   1925                     }
   1926                 }
   1927             }
   1928         }
   1929         assert!(success)
   1930     }
   1931 
   1932     #[test]
   1933     fn test_expanded_c2pa_write_mp4() {
   1934         let mut more_data = "some more test data".as_bytes().to_vec();
   1935         let source = fixture_path("video1.mp4");
   1936 
   1937         let mut success = false;
   1938         if let Ok(temp_dir) = tempdir() {
   1939             let output = temp_dir_path(&temp_dir, "mp4_test.mp4");
   1940 
   1941             if let Ok(_size) = std::fs::copy(&source, &output) {
   1942                 let bmff = BmffIO::new("mp4");
   1943 
   1944                 if let Ok(mut test_data) = bmff.read_cai_store(&source) {
   1945                     test_data.append(&mut more_data);
   1946                     if let Ok(()) = bmff.save_cai_store(&output, &test_data) {
   1947                         if let Ok(read_test_data) = bmff.read_cai_store(&output) {
   1948                             assert!(vec_compare(&test_data, &read_test_data));
   1949                             success = true;
   1950                         }
   1951                     }
   1952                 }
   1953             }
   1954         }
   1955         assert!(success)
   1956     }
   1957 
   1958     #[test]
   1959     fn test_patch_c2pa_write_mp4() {
   1960         let test_data = "some test data".as_bytes();
   1961         let source = fixture_path("video1.mp4");
   1962 
   1963         let mut success = false;
   1964         if let Ok(temp_dir) = tempdir() {
   1965             let output = temp_dir_path(&temp_dir, "mp4_test.mp4");
   1966 
   1967             if let Ok(_size) = std::fs::copy(source, &output) {
   1968                 let bmff = BmffIO::new("mp4");
   1969 
   1970                 if let Ok(source_data) = bmff.read_cai_store(&output) {
   1971                     // create replacement data of same size
   1972                     let mut new_data = vec![0u8; source_data.len()];
   1973                     new_data[..test_data.len()].copy_from_slice(test_data);
   1974                     bmff.patch_cai_store(&output, &new_data).unwrap();
   1975 
   1976                     let replaced = bmff.read_cai_store(&output).unwrap();
   1977 
   1978                     assert_eq!(new_data, replaced);
   1979 
   1980                     success = true;
   1981                 }
   1982             }
   1983         }
   1984         assert!(success)
   1985     }
   1986 
   1987     #[test]
   1988     fn test_remove_c2pa() {
   1989         let source = fixture_path("video1.mp4");
   1990 
   1991         let temp_dir = tempdir().unwrap();
   1992         let output = temp_dir_path(&temp_dir, "mp4_test.mp4");
   1993 
   1994         std::fs::copy(source, &output).unwrap();
   1995         let bmff_io = BmffIO::new("mp4");
   1996 
   1997         bmff_io.remove_cai_store(&output).unwrap();
   1998 
   1999         // read back in asset, JumbfNotFound is expected since it was removed
   2000         match bmff_io.read_cai_store(&output) {
   2001             Err(Error::JumbfNotFound) => (),
   2002             _ => unreachable!(),
   2003         }
   2004     }
   2005 }