use serde::{Deserialize, Serialize};
use crate::c2pa_formats::{
StreamEncapsulation as KernelEncapsulation, StreamMethod as KernelMethod,
};
use crate::c2pa_validate::stream::{verify_stream_safe_with_expected_seeks, StreamVerifyOutput};
use crate::online::{self, NetworkNeed};
use crate::{
map_validate_error, report_from_output, resolve_mime, Error, NetworkReport, ResolvedOptions,
VerificationReport, VerifyOptions, REPORT_SCHEMA_VERSION,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum StreamEncapsulation {
#[serde(rename = "fMP4")]
Fmp4,
#[serde(rename = "CMAF")]
Cmaf,
}
impl StreamEncapsulation {
pub const fn token(self) -> &'static str {
self.kernel().token()
}
pub fn from_token(token: &str) -> Option<Self> {
KernelEncapsulation::from_token(token).map(|value| match value {
KernelEncapsulation::Fmp4 => Self::Fmp4,
KernelEncapsulation::Cmaf => Self::Cmaf,
})
}
const fn kernel(self) -> KernelEncapsulation {
match self {
Self::Fmp4 => KernelEncapsulation::Fmp4,
Self::Cmaf => KernelEncapsulation::Cmaf,
}
}
}
impl std::fmt::Display for StreamEncapsulation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.token())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum StreamMethod {
#[serde(rename = "verifiable-segment-info")]
VerifiableSegmentInfo,
#[serde(rename = "per-segment")]
PerSegment,
}
impl StreamMethod {
pub const fn token(self) -> &'static str {
self.kernel().token()
}
pub fn from_token(token: &str) -> Option<Self> {
KernelMethod::from_token(token).map(|value| match value {
KernelMethod::VerifiableSegmentInfo => Self::VerifiableSegmentInfo,
KernelMethod::PerSegment => Self::PerSegment,
})
}
const fn kernel(self) -> KernelMethod {
match self {
Self::VerifiableSegmentInfo => KernelMethod::VerifiableSegmentInfo,
Self::PerSegment => KernelMethod::PerSegment,
}
}
}
impl std::fmt::Display for StreamMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.token())
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SegmentReport {
pub sequence_number: usize,
pub manifest_label: String,
pub previous_manifest_label: Option<String>,
pub report: VerificationReport,
}
#[derive(Debug, Clone, Serialize)]
pub struct StreamVerificationReport {
pub schema_version: String,
pub encapsulation: StreamEncapsulation,
pub method: StreamMethod,
pub integrity: String,
pub stream: Option<VerificationReport>,
pub segments: Vec<SegmentReport>,
pub chain_valid: Option<bool>,
pub chain_failures: Vec<String>,
pub network: NetworkReport,
}
impl StreamVerificationReport {
pub fn to_json(&self) -> Result<String, Error> {
serde_json::to_string(self).map_err(Error::Serialize)
}
pub fn to_pretty_json(&self) -> Result<String, Error> {
serde_json::to_string_pretty(self).map_err(Error::Serialize)
}
}
pub fn verify_stream(
init_segment: &[u8],
segments: &[&[u8]],
mime_type: &str,
encapsulation: StreamEncapsulation,
method: StreamMethod,
) -> Result<StreamVerificationReport, Error> {
verify_stream_with_options(
init_segment,
segments,
mime_type,
encapsulation,
method,
&VerifyOptions::default(),
)
}
pub fn verify_stream_with_options(
init_segment: &[u8],
segments: &[&[u8]],
mime_type: &str,
encapsulation: StreamEncapsulation,
method: StreamMethod,
options: &VerifyOptions,
) -> Result<StreamVerificationReport, Error> {
verify_stream_with_options_and_expected_seeks(
init_segment,
segments,
&options.expected_seek_positions,
mime_type,
encapsulation,
method,
options,
)
}
#[allow(clippy::too_many_arguments)]
fn verify_stream_with_options_and_expected_seeks(
init_segment: &[u8],
segments: &[&[u8]],
expected_seek_positions: &[usize],
mime_type: &str,
encapsulation: StreamEncapsulation,
method: StreamMethod,
options: &VerifyOptions,
) -> Result<StreamVerificationReport, Error> {
let mime = resolve_mime(mime_type)?;
if crate::c2pa_formats::AssetFormat::from_mime(&mime)
!= Some(crate::c2pa_formats::AssetFormat::Bmff)
{
return Err(Error::UnsupportedMime(mime));
}
let (mut report, needs) = stream_pass(
init_segment,
segments,
expected_seek_positions,
&mime,
encapsulation,
method,
options,
)?;
let (network, evidence) = online::gather(options, &needs);
if evidence.is_empty() {
report.network = network;
return Ok(report);
}
let next = online::apply_evidence(options, &evidence);
let (mut report, _) = stream_pass(
init_segment,
segments,
expected_seek_positions,
&mime,
encapsulation,
method,
&next,
)?;
report.network = network;
Ok(report)
}
#[allow(clippy::too_many_arguments)]
fn stream_pass(
init_segment: &[u8],
segments: &[&[u8]],
expected_seek_positions: &[usize],
mime: &str,
encapsulation: StreamEncapsulation,
method: StreamMethod,
options: &VerifyOptions,
) -> Result<(StreamVerificationReport, Vec<NetworkNeed>), Error> {
let mime = mime.to_string();
let resolved = ResolvedOptions::resolve(options)?;
let output = verify_stream_safe_with_expected_seeks(
&resolved.input(init_segment, &mime),
init_segment,
segments,
expected_seek_positions,
encapsulation.kernel(),
method.kernel(),
resolved.cawg_trust(),
resolved.cawg_allowed_certs(),
resolved.cawg_document_signing_require_anchor,
options.cawg_did_documents.as_ref(),
options.cawg_ica_trusted_issuers.as_deref(),
options.cawg_ica_trust_anchors.as_deref(),
options.cawg_ica_status_lists.as_ref(),
)
.map_err(map_validate_error)?;
Ok(match output {
StreamVerifyOutput::VerifiableSegmentInfo(stream) => {
let needs = online::network_needs(&stream);
let report = report_from_output(*stream, mime, &resolved);
(
StreamVerificationReport {
schema_version: REPORT_SCHEMA_VERSION.to_string(),
encapsulation,
method,
integrity: report.integrity.clone(),
stream: Some(report),
segments: Vec::new(),
chain_valid: None,
chain_failures: Vec::new(),
network: NetworkReport::default(),
},
needs,
)
}
StreamVerifyOutput::PerSegment(per_segment) => {
let mut needs = Vec::new();
let segments: Vec<SegmentReport> = per_segment
.segments
.into_iter()
.map(|segment| {
needs.extend(online::network_needs(&segment.output));
SegmentReport {
sequence_number: segment.sequence_number,
manifest_label: segment.manifest_label,
previous_manifest_label: segment.previous_manifest_label,
report: report_from_output(segment.output, mime.clone(), &resolved),
}
})
.collect();
let integrity = if per_segment.chain_valid
&& segments
.iter()
.all(|segment| segment.report.integrity == "valid")
{
"valid"
} else {
"invalid"
};
(
StreamVerificationReport {
schema_version: REPORT_SCHEMA_VERSION.to_string(),
encapsulation,
method,
integrity: integrity.to_string(),
stream: None,
segments,
chain_valid: Some(per_segment.chain_valid),
chain_failures: per_segment.chain_failures,
network: NetworkReport::default(),
},
needs,
)
}
})
}