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 }