use std::io::{self, Cursor};
use c2pa::{settings::Settings, validation_status, Builder, Reader, Result, ValidationState};
mod common;
#[cfg(all(feature = "add_thumbnails", feature = "file_io"))]
use common::compare_stream_to_known_good;
use common::test_signer;
#[test]
#[cfg(all(feature = "add_thumbnails", feature = "file_io"))]
fn test_builder_ca_jpg() -> Result<()> {
Settings::from_toml(include_str!("../tests/fixtures/test_settings.toml"))?;
const TEST_IMAGE: &[u8] = include_bytes!("fixtures/CA.jpg");
let format = "image/jpeg";
let mut source = Cursor::new(TEST_IMAGE);
let mut builder = Builder::update();
use c2pa::assertions::Action;
builder.add_action(Action::new("c2pa.published"))?;
builder.add_action(serde_json::json!({
"action": "c2pa.edited",
"parameters": {
"description": "edited",
"name": "any value"
},
"softwareAgent": {
"name": "TestApp",
"version": "1.0.0"
}
}))?;
let mut dest = Cursor::new(Vec::new());
builder.sign(&Settings::signer()?, format, &mut source, &mut dest)?;
dest.set_position(0);
compare_stream_to_known_good(&mut dest, format, "CA_test.json")
}
#[test]
fn test_builder_riff() -> Result<()> {
Settings::from_toml(include_str!("../tests/fixtures/test_settings.toml"))?;
let mut source = Cursor::new(include_bytes!("fixtures/sample1.wav"));
let format = "audio/wav";
let mut builder = Builder::update();
builder.definition.claim_version = Some(1); builder.no_embed = true;
builder.sign(&Settings::signer()?, format, &mut source, &mut io::empty())?;
Ok(())
}
#[test]
#[cfg(feature = "file_io")]
fn test_builder_fragmented() -> Result<()> {
use common::tempdirectory;
Settings::from_toml(include_str!("../tests/fixtures/test_settings.toml"))?;
let mut builder = Builder::update();
let tempdir = tempdirectory().expect("temp dir");
let output_path = tempdir.path();
let mut init_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
init_path.push("tests/fixtures/bunny/**/BigBuckBunny_2s_init.mp4");
let pattern = init_path.as_os_str().to_str().unwrap();
for init in glob::glob(pattern).unwrap() {
match init {
Ok(p) => {
let init_dir = p.parent().unwrap();
let pattern_path = init_dir.join("BigBuckBunny_2s*.m4s");
let mut fragments = Vec::new();
for seg in glob::glob(pattern_path.to_str().unwrap())
.unwrap()
.flatten()
{
fragments.push(seg);
}
dbg!(&fragments);
let mut new_output_path =
output_path.join(p.parent().unwrap().file_name().unwrap());
new_output_path.push(p.file_name().unwrap());
builder
.sign_fragmented_files(
&Settings::signer()?,
p.as_path(),
&fragments,
new_output_path.as_path(),
)
.unwrap();
let output_init = new_output_path.join(p.file_name().unwrap());
let output_fragments = fragments
.into_iter()
.map(|f| new_output_path.join(f.file_name().unwrap()))
.collect();
let reader = Reader::from_fragmented_files(&output_init, &output_fragments)?;
assert_eq!(reader.validation_status(), None);
let init_segment = std::fs::File::open(output_init)?;
let fragment = std::fs::File::open(output_fragments[0].as_path())?;
let reader = Reader::from_fragment("video/mp4", init_segment, fragment)?;
assert_eq!(reader.validation_status(), None);
}
Err(e) => panic!("error = {e:?}"),
}
}
Ok(())
}
#[test]
fn test_builder_remote_url_no_embed() -> Result<()> {
Settings::from_toml(include_str!("../tests/fixtures/test_settings.toml"))?;
let mut builder = Builder::update();
Settings::from_toml(
&toml::toml! {
[verify]
remote_manifest_fetch = false
}
.to_string(),
)?;
builder.no_embed = true;
builder.set_remote_url("http://this_does_not_exist/foo.jpg");
const TEST_IMAGE: &[u8] = include_bytes!("fixtures/CA.jpg");
let format = "image/jpeg";
let mut source = Cursor::new(TEST_IMAGE);
let mut dest = Cursor::new(Vec::new());
builder.sign(&Settings::signer()?, format, &mut source, &mut dest)?;
dest.set_position(0);
let reader = Reader::from_stream(format, &mut dest);
if let Err(c2pa::Error::RemoteManifestUrl(url)) = reader {
assert_eq!(url, "http://this_does_not_exist/foo.jpg".to_string());
} else {
panic!("Expected Err(c2pa::Error::RemoteManifestUrl), got {reader:?}");
}
Ok(())
}
#[test]
fn test_builder_embedded_v1_otgp() -> Result<()> {
Settings::from_toml(include_str!("../tests/fixtures/test_settings.toml"))?;
let mut source = Cursor::new(include_bytes!("fixtures/XCA.jpg"));
let format = "image/jpeg";
let mut builder = Builder::update();
let mut dest = Cursor::new(Vec::new());
builder.sign(&Settings::signer()?, format, &mut source, &mut dest)?;
dest.set_position(0);
let reader = Reader::from_stream(format, &mut dest)?;
assert_ne!(reader.validation_state(), ValidationState::Invalid);
assert_eq!(
reader.active_manifest().unwrap().ingredients()[0]
.validation_results()
.unwrap()
.active_manifest()
.unwrap()
.failure[0]
.code(),
validation_status::ASSERTION_DATAHASH_MISMATCH
);
Ok(())
}
#[test]
fn test_dynamic_assertions_builder() -> Result<()> {
use c2pa::{
dynamic_assertion::{DynamicAssertion, DynamicAssertionContent, PartialClaim},
Signer,
SigningAlg,
};
use serde::Serialize;
#[derive(Serialize)]
struct TestAssertion {
my_tag: String,
}
#[derive(Debug)]
struct TestDynamicAssertion {}
impl DynamicAssertion for TestDynamicAssertion {
fn label(&self) -> String {
"com.mycompany.myassertion".to_string()
}
fn reserve_size(&self) -> Result<usize> {
let assertion = TestAssertion {
my_tag: "some value I will replace".to_string(),
};
Ok(serde_json::to_string(&assertion)?.len())
}
fn content(
&self,
_label: &str,
_size: Option<usize>,
claim: &PartialClaim,
) -> Result<DynamicAssertionContent> {
assert!(claim
.assertions()
.inspect(|a| {
dbg!(a);
})
.any(|a| a.url().contains("c2pa.hash")));
let assertion = TestAssertion {
my_tag: "some value I will replace".to_string(),
};
Ok(DynamicAssertionContent::Json(serde_json::to_string(
&assertion,
)?))
}
}
struct DynamicSigner(Box<dyn Signer>);
impl DynamicSigner {
fn new() -> Self {
Self(Box::new(test_signer()))
}
}
impl Signer for DynamicSigner {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
self.0.sign(data)
}
fn alg(&self) -> SigningAlg {
self.0.alg()
}
fn certs(&self) -> crate::Result<Vec<Vec<u8>>> {
self.0.certs()
}
fn reserve_size(&self) -> usize {
self.0.reserve_size()
}
fn time_authority_url(&self) -> Option<String> {
self.0.time_authority_url()
}
fn ocsp_val(&self) -> Option<Vec<u8>> {
self.0.ocsp_val()
}
fn dynamic_assertions(&self) -> Vec<Box<dyn DynamicAssertion>> {
vec![Box::new(TestDynamicAssertion {})]
}
}
let mut builder = Builder::update();
const TEST_IMAGE: &[u8] = include_bytes!("fixtures/CA.jpg");
let format = "image/jpeg";
let mut source = Cursor::new(TEST_IMAGE);
let mut dest = Cursor::new(Vec::new());
let signer = DynamicSigner::new();
builder.sign(&signer, format, &mut source, &mut dest)?;
dest.set_position(0);
let reader = Reader::from_stream(format, &mut dest).unwrap();
println!("reader: {reader}");
assert_ne!(reader.validation_state(), ValidationState::Invalid);
Ok(())
}