From aab027ac9f2ccec2d76b669a25e1156b34b8f92c Mon Sep 17 00:00:00 2001 From: Michael Brogdon Date: Fri, 15 Nov 2024 09:10:17 -0800 Subject: [PATCH] fix: Fix #624 (edge cases when combining the box hashes) (#625) --- sdk/src/assertions/box_hash.rs | 169 ++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 1 deletion(-) diff --git a/sdk/src/assertions/box_hash.rs b/sdk/src/assertions/box_hash.rs index ebb527548..1b4bce91c 100644 --- a/sdk/src/assertions/box_hash.rs +++ b/sdk/src/assertions/box_hash.rs @@ -237,7 +237,22 @@ impl BoxHash { } } - self.boxes = vec![before_c2pa, c2pa_box, after_c2pa]; + // Instead of assuming we can combine all of the different ranges of + // box hashes, we will check the bounds of each one + let mut boxes = Vec::::new(); + // Only add if we have some before the C2PA box + if before_c2pa.range_len > 0 { + boxes.push(before_c2pa); + } + // Do the same for the actual C2PA box + if c2pa_box.range_len > 0 { + boxes.push(c2pa_box); + } + // And finally, add the boxes after the C2PA box + if after_c2pa.range_len > 0 { + boxes.push(after_c2pa); + } + self.boxes = boxes; // compute the hashes for bm in self.boxes.iter_mut() { @@ -457,4 +472,156 @@ mod tests { .verify_stream_hash(&mut input, Some("sha256"), bhp) .unwrap(); } + + // Setup a mock for the AssetBoxHash trait + mockall::mock! { + pub MABH { } + impl AssetBoxHash for MABH { + fn get_box_map(&self, reader: &mut dyn CAIRead) -> Result>; + } + } + + #[test] + fn test_with_no_box_hashes_after_c2pa() { + // Algorithm to use + let alg = "sha256"; + // Create a mock object + let mut mock = MockMABH::new(); + // Setup the expectation when asked for the box map + mock.expect_get_box_map().returning(|_| { + Ok(vec![ + // Make sure the first one is the C2PA box + BoxMap { + names: vec!["C2PA".to_string()], + alg: Some(alg.to_string()), + hash: ByteBuf::from(vec![0]), + pad: ByteBuf::from(vec![]), + range_start: 0, + range_len: 10, + }, + // And follow with + BoxMap { + names: vec!["test".to_string()], + alg: Some(alg.to_string()), + hash: ByteBuf::from(vec![0]), + pad: ByteBuf::from(vec![]), + range_start: 10, + range_len: 10, + }, + ]) + }); + // The data size must match what we return in the expectation + let data = vec![0u8; 20]; + // And create a reader on that data, for the API call + let mut reader = Cursor::new(data); + // Create the BoxHash object + let mut bh = BoxHash { boxes: Vec::new() }; + // And generate the box hashes + let result = bh.generate_box_hash_from_stream(&mut reader, alg, &mock, true); + // We should expect an OK result + assert!(result.is_ok()); + // With a total of 2 boxes + assert_eq!(bh.boxes.len(), 2); + assert_eq!(bh.boxes[0].names[0], "C2PA"); + assert_eq!(bh.boxes[1].names[0], "test"); + } + + #[test] + fn test_with_no_box_hashes_before_c2pa() { + // Algorithm to use + let alg = "sha256"; + // Create a mock object + let mut mock = MockMABH::new(); + // Setup the expectation when asked for the box map + mock.expect_get_box_map().returning(|_| { + Ok(vec![ + // And follow with + BoxMap { + names: vec!["test".to_string()], + alg: Some(alg.to_string()), + hash: ByteBuf::from(vec![0]), + pad: ByteBuf::from(vec![]), + range_start: 0, + range_len: 10, + }, + // Make sure the first one is the C2PA box + BoxMap { + names: vec!["C2PA".to_string()], + alg: Some(alg.to_string()), + hash: ByteBuf::from(vec![0]), + pad: ByteBuf::from(vec![]), + range_start: 10, + range_len: 10, + }, + ]) + }); + // The data size must match what we return in the expectation + let data = vec![0u8; 20]; + // And create a reader on that data, for the API call + let mut reader = Cursor::new(data); + // Create the BoxHash object + let mut bh = BoxHash { boxes: Vec::new() }; + // And generate the box hashes + let result = bh.generate_box_hash_from_stream(&mut reader, alg, &mock, true); + // We should expect an OK result + assert!(result.is_ok()); + // With a total of 2 boxes + assert_eq!(bh.boxes.len(), 2); + assert_eq!(bh.boxes[0].names[0], "test"); + assert_eq!(bh.boxes[1].names[0], "C2PA"); + } + + #[test] + fn test_with_no_box_hashes_before_and_after_c2pa() { + // Algorithm to use + let alg = "sha256"; + // Create a mock object + let mut mock = MockMABH::new(); + // Setup the expectation when asked for the box map + mock.expect_get_box_map().returning(|_| { + Ok(vec![ + // And follow with + BoxMap { + names: vec!["test".to_string()], + alg: Some(alg.to_string()), + hash: ByteBuf::from(vec![0]), + pad: ByteBuf::from(vec![]), + range_start: 0, + range_len: 10, + }, + // Make sure the first one is the C2PA box + BoxMap { + names: vec!["C2PA".to_string()], + alg: Some(alg.to_string()), + hash: ByteBuf::from(vec![0]), + pad: ByteBuf::from(vec![]), + range_start: 10, + range_len: 10, + }, + BoxMap { + names: vec!["test1".to_string()], + alg: Some(alg.to_string()), + hash: ByteBuf::from(vec![0]), + pad: ByteBuf::from(vec![]), + range_start: 20, + range_len: 10, + }, + ]) + }); + // The data size must match what we return in the expectation + let data = vec![0u8; 30]; + // And create a reader on that data, for the API call + let mut reader = Cursor::new(data); + // Create the BoxHash object + let mut bh = BoxHash { boxes: Vec::new() }; + // And generate the box hashes + let result = bh.generate_box_hash_from_stream(&mut reader, alg, &mock, true); + // We should expect an OK result + assert!(result.is_ok()); + // With a total of 2 boxes + assert_eq!(bh.boxes.len(), 3); + assert_eq!(bh.boxes[0].names[0], "test"); + assert_eq!(bh.boxes[1].names[0], "C2PA"); + assert_eq!(bh.boxes[2].names[0], "test1"); + } }