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_hash.rs (32545B)


      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,
     16     collections::{hash_map::Entry::Vacant, HashMap},
     17     fmt, fs,
     18     io::{BufReader, Cursor, SeekFrom},
     19     ops::Deref,
     20     path::Path,
     21 };
     22 
     23 use mp4::*;
     24 use serde::{
     25     de::{SeqAccess, Visitor},
     26     ser::SerializeSeq,
     27     Deserialize, Deserializer, Serialize, Serializer,
     28 };
     29 use serde_bytes::ByteBuf;
     30 use sha2::{Digest, Sha256, Sha384, Sha512};
     31 
     32 use crate::{
     33     assertion::{Assertion, AssertionBase, AssertionCbor},
     34     assertions::labels,
     35     asset_handlers::bmff_io::{bmff_to_jumbf_exclusions, read_bmff_c2pa_boxes, BoxInfoLite},
     36     asset_io::CAIRead,
     37     cbor_types::UriT,
     38     utils::{
     39         hash_utils::{
     40             concat_and_hash, hash_stream_by_alg, vec_compare, verify_stream_by_alg, HashRange,
     41             Hasher,
     42         },
     43         merkle::C2PAMerkleTree,
     44     },
     45     Error,
     46 };
     47 
     48 const ASSERTION_CREATION_VERSION: usize = 2;
     49 
     50 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
     51 pub struct ExclusionsMap {
     52     pub xpath: String,
     53     pub length: Option<u32>,
     54     pub data: Option<Vec<DataMap>>,
     55     pub subset: Option<Vec<SubsetMap>>,
     56     pub version: Option<u8>,
     57     pub flags: Option<ByteBuf>,
     58     pub exact: Option<bool>,
     59 }
     60 
     61 impl ExclusionsMap {
     62     pub const fn new(xpath: String) -> Self {
     63         ExclusionsMap {
     64             xpath,
     65             length: None,
     66             data: None,
     67             subset: None,
     68             version: None,
     69             flags: None,
     70             exact: None,
     71         }
     72     }
     73 }
     74 
     75 #[derive(Clone, Debug, PartialEq, Eq)]
     76 pub struct VecByteBuf(Vec<ByteBuf>);
     77 
     78 impl Deref for VecByteBuf {
     79     type Target = Vec<ByteBuf>;
     80 
     81     fn deref(&self) -> &Vec<ByteBuf> {
     82         &self.0
     83     }
     84 }
     85 
     86 impl Serialize for VecByteBuf {
     87     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     88     where
     89         S: Serializer,
     90     {
     91         let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
     92         for e in &self.0 {
     93             seq.serialize_element(e)?;
     94         }
     95         seq.end()
     96     }
     97 }
     98 
     99 struct VecByteBufVisitor;
    100 
    101 impl<'de> Visitor<'de> for VecByteBufVisitor {
    102     type Value = VecByteBuf;
    103 
    104     fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
    105         formatter.write_str("Vec<ByteBuf>")
    106     }
    107 
    108     fn visit_seq<V>(self, mut visitor: V) -> std::result::Result<Self::Value, V::Error>
    109     where
    110         V: SeqAccess<'de>,
    111     {
    112         let len = cmp::min(visitor.size_hint().unwrap_or(0), 4096);
    113         let mut byte_bufs: Vec<ByteBuf> = Vec::with_capacity(len);
    114 
    115         while let Some(b) = visitor.next_element()? {
    116             byte_bufs.push(b);
    117         }
    118 
    119         Ok(VecByteBuf(byte_bufs))
    120     }
    121 }
    122 
    123 impl<'de> Deserialize<'de> for VecByteBuf {
    124     fn deserialize<D>(deserializer: D) -> std::result::Result<VecByteBuf, D::Error>
    125     where
    126         D: Deserializer<'de>,
    127     {
    128         deserializer.deserialize_seq(VecByteBufVisitor {})
    129     }
    130 }
    131 
    132 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    133 pub struct MerkleMap {
    134     #[serde(rename = "uniqueId")]
    135     pub unique_id: u32,
    136 
    137     #[serde(rename = "localId")]
    138     pub local_id: u32,
    139 
    140     pub count: u32,
    141 
    142     #[serde(skip_serializing_if = "Option::is_none")]
    143     pub alg: Option<String>,
    144 
    145     #[serde(rename = "initHash", skip_serializing_if = "Option::is_none")]
    146     pub init_hash: Option<ByteBuf>,
    147 
    148     pub hashes: VecByteBuf,
    149 }
    150 
    151 impl MerkleMap {
    152     pub fn hash_check(&self, indx: u32, merkle_hash: &[u8]) -> bool {
    153         if let Some(h) = self.hashes.get(indx as usize) {
    154             vec_compare(h, merkle_hash)
    155         } else {
    156             false
    157         }
    158     }
    159 
    160     pub fn check_merkle_tree(
    161         &self,
    162         alg: &str,
    163         hash: &[u8],
    164         location: u32,
    165         proof: &Option<VecByteBuf>,
    166     ) -> bool {
    167         if location >= self.count {
    168             return false;
    169         }
    170 
    171         let mut index = location;
    172         let mut hash = hash.to_vec();
    173         let layers = C2PAMerkleTree::to_layout(self.count as usize);
    174 
    175         if let Some(hashes) = proof {
    176             // playback proof
    177             let mut proof_index = 0;
    178             for layer in layers {
    179                 let is_right = index % 2 == 1;
    180 
    181                 if layer == self.hashes.len() {
    182                     break;
    183                 }
    184 
    185                 if is_right {
    186                     if index - 1 < layer as u32 {
    187                         // make sure proof structure is valid
    188                         if let Some(proof_hash) = hashes.get(proof_index) {
    189                             hash = concat_and_hash(alg, proof_hash, Some(&hash));
    190                             proof_index += 1;
    191                         } else {
    192                             return false;
    193                         }
    194                     }
    195                 } else if index + 1 < layer as u32 {
    196                     // make sure proof structure is valid
    197                     if let Some(proof_hash) = hashes.get(proof_index) {
    198                         hash = concat_and_hash(alg, &hash, Some(proof_hash));
    199                         proof_index += 1;
    200                     } else {
    201                         return false;
    202                     }
    203                 }
    204 
    205                 index /= 2;
    206             }
    207         } else {
    208             //empty proof playback
    209             for layer in layers {
    210                 if layer == self.hashes.len() {
    211                     break;
    212                 }
    213                 index /= 2;
    214             }
    215         }
    216 
    217         self.hash_check(index, &hash)
    218     }
    219 }
    220 
    221 #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
    222 pub struct BmffMerkleMap {
    223     #[serde(rename = "uniqueId")]
    224     pub unique_id: u32,
    225 
    226     #[serde(rename = "localId")]
    227     pub local_id: u32,
    228 
    229     pub location: u32,
    230 
    231     pub hashes: Option<VecByteBuf>,
    232 }
    233 
    234 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    235 pub struct DataMap {
    236     pub offset: u32,
    237     #[serde(with = "serde_bytes")]
    238     pub value: Vec<u8>,
    239 }
    240 
    241 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    242 pub struct SubsetMap {
    243     pub offset: u32,
    244     pub length: u32,
    245 }
    246 
    247 /// Helper class to create BmffHash assertion. (These are auto-generated by the SDK.)
    248 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    249 pub struct BmffHash {
    250     exclusions: Vec<ExclusionsMap>,
    251 
    252     #[serde(skip_serializing_if = "Option::is_none")]
    253     alg: Option<String>,
    254 
    255     #[serde(skip_serializing_if = "Option::is_none")]
    256     hash: Option<ByteBuf>,
    257 
    258     #[serde(skip_serializing_if = "Option::is_none")]
    259     merkle: Option<Vec<MerkleMap>>,
    260 
    261     #[serde(skip_serializing_if = "Option::is_none")]
    262     name: Option<String>,
    263 
    264     #[serde(skip_serializing)]
    265     url: Option<UriT>, // deprecated in V2 and not to be used
    266 
    267     #[serde(skip)]
    268     bmff_version: usize,
    269 }
    270 
    271 impl BmffHash {
    272     pub const LABEL: &'static str = labels::BMFF_HASH;
    273 
    274     pub fn new(name: &str, alg: &str, url: Option<UriT>) -> Self {
    275         BmffHash {
    276             exclusions: Vec::new(),
    277             alg: Some(alg.to_string()),
    278             hash: None,
    279             merkle: None,
    280             name: Some(name.to_string()),
    281             url,
    282             bmff_version: ASSERTION_CREATION_VERSION,
    283         }
    284     }
    285 
    286     pub fn exclusions(&self) -> &[ExclusionsMap] {
    287         self.exclusions.as_ref()
    288     }
    289 
    290     pub fn exclusions_mut(&mut self) -> &mut Vec<ExclusionsMap> {
    291         &mut self.exclusions
    292     }
    293 
    294     pub const fn alg(&self) -> Option<&String> {
    295         self.alg.as_ref()
    296     }
    297 
    298     pub fn hash(&self) -> Option<&Vec<u8>> {
    299         self.hash.as_deref()
    300     }
    301 
    302     pub const fn merkle(&self) -> Option<&Vec<MerkleMap>> {
    303         self.merkle.as_ref()
    304     }
    305 
    306     pub fn set_hash(&mut self, hash: Vec<u8>) {
    307         self.hash = Some(ByteBuf::from(hash));
    308     }
    309 
    310     pub const fn name(&self) -> Option<&String> {
    311         self.name.as_ref()
    312     }
    313 
    314     pub const fn url(&self) -> Option<&UriT> {
    315         self.url.as_ref()
    316     }
    317 
    318     pub const fn bmff_version(&self) -> usize {
    319         self.bmff_version
    320     }
    321 
    322     fn set_bmff_version(&mut self, version: usize) {
    323         self.bmff_version = version;
    324     }
    325 
    326     /// Returns `true` if this is a remote hash.
    327     pub const fn is_remote_hash(&self) -> bool {
    328         self.url.is_some()
    329     }
    330 
    331     pub fn set_merkle(&mut self, merkle: Vec<MerkleMap>) {
    332         self.merkle = Some(merkle);
    333     }
    334 
    335     /// Generate the hash value for the asset using the range from the BmffHash.
    336     pub fn gen_hash_from_stream(&mut self, stream: &mut dyn CAIRead) -> crate::error::Result<()> {
    337         self.hash = Some(ByteBuf::from(self.hash_from_stream(stream)?));
    338         //self.path = PathBuf::from(asset_path);
    339         Ok(())
    340     }
    341 
    342     /// Generate the hash value for the asset using the range from the BmffHash.
    343     #[cfg(feature = "file_io")]
    344     pub fn gen_hash(&mut self, asset_path: &Path) -> crate::error::Result<()> {
    345         let mut file = std::fs::File::open(asset_path)?;
    346         self.hash = Some(ByteBuf::from(self.hash_from_stream(&mut file)?));
    347         //self.path = PathBuf::from(asset_path);
    348         Ok(())
    349     }
    350 
    351     /// Generate the asset hash from an asset stream using the constructed
    352     /// start and length values.
    353     fn hash_from_stream(&mut self, stream: &mut dyn CAIRead) -> crate::error::Result<Vec<u8>> {
    354         if self.is_remote_hash() {
    355             return Err(Error::BadParam(
    356                 "asset hash is remote, not yet supported".to_owned(),
    357             ));
    358         }
    359 
    360         let alg = match self.alg {
    361             Some(ref a) => a.clone(),
    362             None => "sha256".to_string(),
    363         };
    364 
    365         let bmff_exclusions = &self.exclusions;
    366 
    367         // convert BMFF exclusion map to flat exclusion list
    368         let exclusions = bmff_to_jumbf_exclusions(stream, bmff_exclusions, self.bmff_version > 1)?;
    369 
    370         let hash = hash_stream_by_alg(&alg, stream, Some(exclusions), true)?;
    371 
    372         if hash.is_empty() {
    373             Err(Error::BadParam("could not generate data hash".to_string()))
    374         } else {
    375             Ok(hash)
    376         }
    377     }
    378 
    379     pub fn verify_in_memory_hash(
    380         &self,
    381         data: &[u8],
    382         alg: Option<&str>,
    383     ) -> crate::error::Result<()> {
    384         let mut reader = Cursor::new(data);
    385 
    386         self.verify_stream_hash(&mut reader, alg)
    387     }
    388 
    389     // The BMFFMerklMaps are stored contiguous in the file.  Break this Vec into groups based on
    390     // the MerkleMap it matches.
    391     fn split_bmff_merkle_map(
    392         &self,
    393         bmff_merkle_map: Vec<BmffMerkleMap>,
    394     ) -> crate::Result<HashMap<u32, Vec<BmffMerkleMap>>> {
    395         let mut current = bmff_merkle_map;
    396         let mut output = HashMap::new();
    397         if let Some(mm) = self.merkle() {
    398             for m in mm {
    399                 let rest = current.split_off(m.count as usize);
    400 
    401                 if current.len() == m.count as usize {
    402                     output.insert(m.local_id, current.to_owned());
    403                 } else {
    404                     return Err(Error::HashMismatch("MerkleMap count incorrect".to_string()));
    405                 }
    406                 current = rest;
    407             }
    408         } else {
    409             output.insert(0, current);
    410         }
    411         Ok(output)
    412     }
    413 
    414     // Breaks box runs at fragment boundaries (moof boxes)
    415     fn split_fragment_boxes(boxes: &[BoxInfoLite]) -> Vec<Vec<BoxInfoLite>> {
    416         let mut moof_list = Vec::new();
    417 
    418         // start from 1st moof
    419         if let Some(pos) = boxes.iter().position(|b| b.path == "moof") {
    420             let mut box_list = vec![boxes[pos].clone()];
    421 
    422             if pos == 0 {
    423                 return moof_list; // this does not contain fragmented content
    424             }
    425 
    426             for b in boxes[pos + 1..].iter() {
    427                 if b.path == "moof" {
    428                     moof_list.push(box_list); // save box list
    429                     box_list = Vec::new(); // start new box list
    430                 }
    431                 box_list.push(b.clone());
    432             }
    433             moof_list.push(box_list); // save last list
    434         }
    435         moof_list
    436     }
    437 
    438     pub fn verify_hash(&self, asset_path: &Path, alg: Option<&str>) -> crate::error::Result<()> {
    439         let mut data = fs::File::open(asset_path)?;
    440         self.verify_stream_hash(&mut data, alg)
    441     }
    442 
    443     /* Verifies BMFF hashes from a single file asset.  The following variants are handled
    444         A single BMFF asset with only a file hash
    445         A single BMMF asset with Merkle tree hash
    446             Timed media (Merkle hashes over track chunks)
    447             Untimed media (Merkle hashes over iloc locations)
    448         A single BMFF asset containing all fragments (Merkle hashes over moof ranges).
    449     */
    450     pub fn verify_stream_hash(
    451         &self,
    452         reader: &mut dyn CAIRead,
    453         alg: Option<&str>,
    454     ) -> crate::error::Result<()> {
    455         if self.is_remote_hash() {
    456             return Err(Error::BadParam(
    457                 "asset hash is remote, not yet supported".to_owned(),
    458             ));
    459         }
    460 
    461         reader.rewind()?;
    462         let size = stream_len(reader)?;
    463 
    464         let curr_alg = match &self.alg {
    465             Some(a) => a.clone(),
    466             None => match alg {
    467                 Some(a) => a.to_owned(),
    468                 None => "sha256".to_string(),
    469             },
    470         };
    471 
    472         // convert BMFF exclusion map to flat exclusion list
    473         let exclusions = bmff_to_jumbf_exclusions(reader, &self.exclusions, self.bmff_version > 1)?;
    474 
    475         // handle file level hashing
    476         if let Some(hash) = self.hash() {
    477             if !verify_stream_by_alg(&curr_alg, hash, reader, Some(exclusions.clone()), true) {
    478                 return Err(Error::HashMismatch(
    479                     "BMFF file level hash mismatch".to_string(),
    480                 ));
    481             }
    482         }
    483 
    484         // merkle hashed BMFF
    485         if let Some(mm_vec) = self.merkle() {
    486             // get merkle boxes from asset
    487             let c2pa_boxes = read_bmff_c2pa_boxes(reader)?;
    488             let bmff_merkle = c2pa_boxes.bmff_merkle;
    489             let box_infos = c2pa_boxes.box_infos;
    490 
    491             let first_moof = box_infos.iter().find(|b| b.path == "moof");
    492             let is_fragmented = first_moof.is_some();
    493 
    494             // check initialization segments (must do here in separate loop since MP4 will consume the reader)
    495             for mm in mm_vec {
    496                 let alg = match &mm.alg {
    497                     Some(a) => a,
    498                     None => self
    499                         .alg()
    500                         .ok_or(Error::HashMismatch("no algorithm found".to_string()))?,
    501                 };
    502 
    503                 if let Some(init_hash) = &mm.init_hash {
    504                     if let Some(moof_box) = first_moof {
    505                         // add the moof to end exclusion
    506                         let moof_exclusion = HashRange::new(
    507                             moof_box.offset as usize,
    508                             (size - moof_box.offset) as usize,
    509                         );
    510 
    511                         let mut mm_exclusions = exclusions.clone();
    512                         mm_exclusions.push(moof_exclusion);
    513 
    514                         if !verify_stream_by_alg(alg, init_hash, reader, Some(mm_exclusions), true)
    515                         {
    516                             return Err(Error::HashMismatch(
    517                                 "BMFF file level hash mismatch".to_string(),
    518                             ));
    519                         }
    520                     } else {
    521                         return Err(Error::HashMismatch(
    522                             "BMFF inithash must not be present for non-fragmented media".to_owned(),
    523                         ));
    524                     }
    525                 }
    526             }
    527 
    528             // is this a fragmented BMFF
    529             if is_fragmented {
    530                 for mm in mm_vec {
    531                     let alg = match &mm.alg {
    532                         Some(a) => a,
    533                         None => self
    534                             .alg()
    535                             .ok_or(Error::HashMismatch("no algorithm found".to_string()))?,
    536                     };
    537 
    538                     let moof_chunks = BmffHash::split_fragment_boxes(&box_infos);
    539 
    540                     // make sure there is a 1-1 mapping of moof chunks and Merkle values
    541                     if moof_chunks.len() != mm.count as usize
    542                         || bmff_merkle.len() != mm.count as usize
    543                     {
    544                         return Err(Error::HashMismatch(
    545                             "Incorrect number of fragments hashes".to_owned(),
    546                         ));
    547                     }
    548 
    549                     // build Merkle tree for the moof chucks minus the excluded ranges
    550                     for (index, boxes) in moof_chunks.iter().enumerate() {
    551                         // include just the range of this chunk so exclude boxes before and after
    552                         let mut curr_exclusions = exclusions.clone();
    553 
    554                         // before box exclusion starts at beginning of file until the start of this chunk
    555                         let before_box_start = 0;
    556                         let before_box_len = match boxes.first() {
    557                             Some(first) => first.offset as usize,
    558                             None => 0,
    559                         };
    560                         let before_box_exclusion = HashRange::new(before_box_start, before_box_len);
    561                         curr_exclusions.push(before_box_exclusion);
    562 
    563                         // after box exclusion continues to the end of the file
    564                         let after_box_start = match boxes.last() {
    565                             Some(last) => last.offset + last.size,
    566                             None => 0,
    567                         };
    568                         let after_box_len = size - after_box_start;
    569                         let after_box_exclusion =
    570                             HashRange::new(after_box_start as usize, after_box_len as usize);
    571                         curr_exclusions.push(after_box_exclusion);
    572 
    573                         // hash the specified range
    574                         let hash = hash_stream_by_alg(alg, reader, Some(curr_exclusions), true)?;
    575 
    576                         let bmff_mm = &bmff_merkle[index];
    577 
    578                         // check MerkleMap for the hash
    579                         if !mm.check_merkle_tree(alg, &hash, bmff_mm.location, &bmff_mm.hashes) {
    580                             return Err(Error::HashMismatch("Fragment not valid".to_string()));
    581                         }
    582                     }
    583                 }
    584                 return Ok(());
    585             } else if box_infos.iter().any(|b| b.path == "moov") {
    586                 // timed media case
    587 
    588                 let track_to_bmff_merkle_map = if bmff_merkle.is_empty() {
    589                     HashMap::new()
    590                 } else {
    591                     self.split_bmff_merkle_map(bmff_merkle)?
    592                 };
    593 
    594                 reader.rewind()?;
    595                 let buf_reader = BufReader::new(reader);
    596                 let mut mp4 = mp4::Mp4Reader::read_header(buf_reader, size)
    597                     .map_err(|_e| Error::InvalidAsset("Could not parse BMFF".to_string()))?;
    598                 let track_count = mp4.tracks().len();
    599 
    600                 for mm in mm_vec {
    601                     let alg = match &mm.alg {
    602                         Some(a) => a,
    603                         None => self
    604                             .alg()
    605                             .ok_or(Error::HashMismatch("no algorithm found".to_string()))?,
    606                     };
    607 
    608                     if track_count > 0 {
    609                         // timed media case
    610                         let track = {
    611                             // clone so we can borrow later
    612                             let tt = mp4.tracks().get(&mm.local_id).ok_or(Error::HashMismatch(
    613                                 "Merkle location not found".to_owned(),
    614                             ))?;
    615 
    616                             Mp4Track {
    617                                 trak: tt.trak.clone(),
    618                                 trafs: tt.trafs.clone(),
    619                                 default_sample_duration: tt.default_sample_duration,
    620                             }
    621                         };
    622 
    623                         let sample_cnt = track.sample_count();
    624                         if sample_cnt == 0 {
    625                             return Err(Error::InvalidAsset("No samples".to_string()));
    626                         }
    627 
    628                         let track_id = track.track_id();
    629 
    630                         // create sample to chunk mapping
    631                         // create the Merkle tree per samples in a chunk
    632                         let mut chunk_hash_map: HashMap<u32, Hasher> = HashMap::new();
    633                         let stsc = &track.trak.mdia.minf.stbl.stsc;
    634                         for sample_id in 1..=sample_cnt {
    635                             let stsc_idx = stsc_index(&track, sample_id)?;
    636 
    637                             let stsc_entry = &stsc.entries[stsc_idx];
    638 
    639                             let first_chunk = stsc_entry.first_chunk;
    640                             let first_sample = stsc_entry.first_sample;
    641                             let samples_per_chunk = stsc_entry.samples_per_chunk;
    642 
    643                             let chunk_id =
    644                                 first_chunk + (sample_id - first_sample) / samples_per_chunk;
    645 
    646                             // add chunk Hasher if needed
    647                             if let Vacant(e) = chunk_hash_map.entry(chunk_id) {
    648                                 // get hasher for algorithm
    649                                 let hasher_enum = match alg.as_str() {
    650                                     "sha256" => Hasher::SHA256(Sha256::new()),
    651                                     "sha384" => Hasher::SHA384(Sha384::new()),
    652                                     "sha512" => Hasher::SHA512(Sha512::new()),
    653                                     _ => {
    654                                         return Err(Error::HashMismatch(
    655                                             "no algorithm found".to_string(),
    656                                         ))
    657                                     }
    658                                 };
    659 
    660                                 e.insert(hasher_enum);
    661                             }
    662 
    663                             if let Ok(Some(sample)) = &mp4.read_sample(track_id, sample_id) {
    664                                 let h = chunk_hash_map.get_mut(&chunk_id).ok_or(
    665                                     Error::HashMismatch(
    666                                         "Bad Merkle tree sample mapping".to_string(),
    667                                     ),
    668                                 )?;
    669                                 // add sample data to hash
    670                                 h.update(&sample.bytes);
    671                             } else {
    672                                 return Err(Error::HashMismatch(
    673                                     "Merle location not found".to_owned(),
    674                                 ));
    675                             }
    676                         }
    677 
    678                         // finalize leaf hashes
    679                         let mut leaf_hashes = Vec::new();
    680                         for chunk_bmff_mm in &track_to_bmff_merkle_map[&track_id] {
    681                             match chunk_hash_map.remove(&(chunk_bmff_mm.location + 1)) {
    682                                 Some(h) => {
    683                                     let h = Hasher::finalize(h);
    684                                     leaf_hashes.push(h);
    685                                 }
    686                                 None => {
    687                                     return Err(Error::HashMismatch(
    688                                         "Could not generate hash".to_owned(),
    689                                     ))
    690                                 }
    691                             }
    692                         }
    693 
    694                         for chunk_bmff_mm in &track_to_bmff_merkle_map[&track_id] {
    695                             let hash = &leaf_hashes[chunk_bmff_mm.location as usize];
    696 
    697                             // check MerkleMap for the hash
    698                             if !mm.check_merkle_tree(
    699                                 alg,
    700                                 hash,
    701                                 chunk_bmff_mm.location,
    702                                 &chunk_bmff_mm.hashes,
    703                             ) {
    704                                 return Err(Error::HashMismatch("Fragment not valid".to_string()));
    705                             }
    706                         }
    707                     }
    708                 }
    709             } else {
    710                 // non-timed media so use iloc (awaiting use case/example since the iloc varies by format)
    711                 return Err(Error::HashMismatch(
    712                     "Merkle iloc not yet supported".to_owned(),
    713                 ));
    714             }
    715         }
    716 
    717         Ok(())
    718     }
    719 
    720     // Used to verify fragmented BMFF assets spread across multiple file.
    721     pub fn verify_stream_segment(
    722         &self,
    723         init_stream: &mut dyn CAIRead,
    724         fragment_stream: &mut dyn CAIRead,
    725         alg: Option<&str>,
    726     ) -> crate::Result<()> {
    727         let curr_alg = match &self.alg {
    728             Some(a) => a.clone(),
    729             None => match alg {
    730                 Some(a) => a.to_owned(),
    731                 None => "sha256".to_string(),
    732             },
    733         };
    734 
    735         // handle file level hashing
    736         if self.hash().is_some() {
    737             return Err(Error::HashMismatch(
    738                 "Hash value should not be present for a fragmented BMFF asset".to_string(),
    739             ));
    740         }
    741 
    742         // Merkle hashed BMFF
    743         if let Some(mm_vec) = self.merkle() {
    744             // get merkle boxes from segment
    745             let c2pa_boxes = read_bmff_c2pa_boxes(fragment_stream)?;
    746             let bmff_merkle = c2pa_boxes.bmff_merkle;
    747 
    748             if bmff_merkle.is_empty() {
    749                 return Err(Error::HashMismatch("Fragment had no MerkleMap".to_string()));
    750             }
    751 
    752             for bmff_mm in bmff_merkle {
    753                 // find matching MerkleMap for this uniqueId & localId
    754                 if let Some(mm) = mm_vec
    755                     .iter()
    756                     .find(|mm| mm.unique_id == bmff_mm.unique_id && mm.local_id == bmff_mm.local_id)
    757                 {
    758                     let alg = match &mm.alg {
    759                         Some(a) => a,
    760                         None => &curr_alg,
    761                     };
    762 
    763                     // check the inithash (for fragmented MP4 with multiple files this is the hash of the init_segment minus any exclusions)
    764                     if let Some(init_hash) = &mm.init_hash {
    765                         let bmff_exclusions = &self.exclusions;
    766 
    767                         // convert BMFF exclusion map to flat exclusion list
    768                         init_stream.rewind()?;
    769                         let exclusions = bmff_to_jumbf_exclusions(
    770                             init_stream,
    771                             bmff_exclusions,
    772                             self.bmff_version > 1,
    773                         )?;
    774 
    775                         if !verify_stream_by_alg(
    776                             alg,
    777                             init_hash,
    778                             init_stream,
    779                             Some(exclusions),
    780                             true,
    781                         ) {
    782                             return Err(Error::HashMismatch("BMFF inithash mismatch".to_string()));
    783                         }
    784 
    785                         let fragment_exclusions = bmff_to_jumbf_exclusions(
    786                             fragment_stream,
    787                             bmff_exclusions,
    788                             self.bmff_version > 1,
    789                         )?;
    790 
    791                         // hash the entire fragment minus exclusions
    792                         let hash = hash_stream_by_alg(
    793                             alg,
    794                             fragment_stream,
    795                             Some(fragment_exclusions),
    796                             true,
    797                         )?;
    798 
    799                         // check MerkleMap for the hash
    800                         if !mm.check_merkle_tree(alg, &hash, bmff_mm.location, &bmff_mm.hashes) {
    801                             return Err(Error::HashMismatch("Fragment not valid".to_string()));
    802                         }
    803                     }
    804                 } else {
    805                     return Err(Error::HashMismatch("Fragment had no MerkleMap".to_string()));
    806                 }
    807             }
    808         } else {
    809             return Err(Error::HashMismatch(
    810                 "Merkle value must be present for a fragmented BMFF asset".to_string(),
    811             ));
    812         }
    813 
    814         Ok(())
    815     }
    816 }
    817 
    818 impl AssertionCbor for BmffHash {}
    819 
    820 impl AssertionBase for BmffHash {
    821     const LABEL: &'static str = Self::LABEL;
    822     const VERSION: Option<usize> = Some(ASSERTION_CREATION_VERSION);
    823 
    824     // todo: this mechanism needs to change since a struct could support different versions
    825 
    826     fn to_assertion(&self) -> crate::error::Result<Assertion> {
    827         Self::to_cbor_assertion(self)
    828     }
    829 
    830     fn from_assertion(assertion: &Assertion) -> crate::error::Result<Self> {
    831         let mut bmff_hash = Self::from_cbor_assertion(assertion)?;
    832         bmff_hash.set_bmff_version(assertion.get_ver().unwrap_or(1));
    833 
    834         Ok(bmff_hash)
    835     }
    836 }
    837 
    838 fn stsc_index(track: &Mp4Track, sample_id: u32) -> crate::Result<usize> {
    839     if track.trak.mdia.minf.stbl.stsc.entries.is_empty() {
    840         return Err(Error::InvalidAsset("BMFF has no stsc entries".to_string()));
    841     }
    842     for (i, entry) in track.trak.mdia.minf.stbl.stsc.entries.iter().enumerate() {
    843         if sample_id < entry.first_sample {
    844             return if i == 0 {
    845                 Err(Error::InvalidAsset("BMFF no sample not found".to_string()))
    846             } else {
    847                 Ok(i - 1)
    848             };
    849         }
    850     }
    851     Ok(track.trak.mdia.minf.stbl.stsc.entries.len() - 1)
    852 }
    853 
    854 fn stream_len(reader: &mut dyn CAIRead) -> crate::Result<u64> {
    855     let old_pos = reader.stream_position()?;
    856     let len = reader.seek(SeekFrom::End(0))?;
    857 
    858     if old_pos != len {
    859         reader.seek(SeekFrom::Start(old_pos))?;
    860     }
    861 
    862     Ok(len)
    863 }
    864 
    865 /* we need shippable examples
    866 #[cfg(test)]
    867 pub mod tests {
    868     #![allow(clippy::expect_used)]
    869     #![allow(clippy::panic)]
    870     #![allow(clippy::unwrap_used)]
    871 
    872     //use tempfile::tempdir;
    873 
    874     //use super::*;
    875     use crate::utils::test::fixture_path;
    876 
    877     #[cfg(not(target_arch = "wasm32"))]
    878     #[test]
    879     fn test_fragemented_mp4() {
    880         use crate::{
    881             assertions::BmffHash, asset_handlers::bmff_io::BmffIO, asset_io::AssetIO,
    882             status_tracker::DetailedStatusTracker, store::Store, AssertionBase,
    883         };
    884 
    885         let init_stream_path = fixture_path("dashinit.mp4");
    886         let segment_stream_path = fixture_path("dash1.m4s");
    887         let segment_stream_path10 = fixture_path("dash10.m4s");
    888         let segment_stream_path11 = fixture_path("dash11.m4s");
    889 
    890 
    891         let mut init_stream = std::fs::File::open(init_stream_path).unwrap();
    892         let mut segment_stream = std::fs::File::open(segment_stream_path).unwrap();
    893         let mut segment_stream10 = std::fs::File::open(segment_stream_path10).unwrap();
    894         let mut segment_stream11 = std::fs::File::open(segment_stream_path11).unwrap();
    895 
    896 
    897         let mut log = DetailedStatusTracker::default();
    898 
    899         let bmff_io = BmffIO::new("mp4");
    900         let bmff_handler = bmff_io.get_reader();
    901 
    902         let manifest_bytes = bmff_handler.read_cai(&mut init_stream).unwrap();
    903         let store = Store::from_jumbf(&manifest_bytes, &mut log).unwrap();
    904 
    905         // get the bmff hashes
    906         let claim = store.provenance_claim().unwrap();
    907         for dh_assertion in claim.hash_assertions() {
    908             if dh_assertion.label_root() == BmffHash::LABEL {
    909                 let bmff_hash = BmffHash::from_assertion(dh_assertion).unwrap();
    910 
    911                 bmff_hash
    912                     .verify_stream_segment(&mut init_stream, &mut segment_stream, None)
    913                     .unwrap();
    914 
    915                 bmff_hash
    916                     .verify_stream_segment(&mut init_stream, &mut segment_stream10, None)
    917                     .unwrap();
    918 
    919                 bmff_hash
    920                     .verify_stream_segment(&mut init_stream, &mut segment_stream11, None)
    921                     .unwrap();
    922             }
    923         }
    924     }
    925 }
    926 */