1use serde::{Deserialize, Serialize};
16use time::OffsetDateTime;
17
18use crate::c2pa_formats::{
19 StreamEncapsulation as KernelEncapsulation, StreamMethod as KernelMethod,
20};
21use crate::c2pa_validate::stream::{verify_stream_safe_with_expected_seeks, StreamVerifyOutput};
22use crate::online::{self, NetworkNeed};
23use crate::{
24 map_validate_error, parse_stream_presentation_times, report_from_output, resolve_mime, Error,
25 NetworkReport, ResolvedOptions, VerificationReport, VerifyOptions, REPORT_SCHEMA_VERSION,
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum StreamEncapsulation {
36 #[serde(rename = "fMP4")]
39 Fmp4,
40 #[serde(rename = "CMAF")]
42 Cmaf,
43}
44
45impl StreamEncapsulation {
46 pub const fn token(self) -> &'static str {
48 self.kernel().token()
49 }
50
51 pub fn from_token(token: &str) -> Option<Self> {
53 KernelEncapsulation::from_token(token).map(|value| match value {
54 KernelEncapsulation::Fmp4 => Self::Fmp4,
55 KernelEncapsulation::Cmaf => Self::Cmaf,
56 })
57 }
58
59 const fn kernel(self) -> KernelEncapsulation {
60 match self {
61 Self::Fmp4 => KernelEncapsulation::Fmp4,
62 Self::Cmaf => KernelEncapsulation::Cmaf,
63 }
64 }
65}
66
67impl std::fmt::Display for StreamEncapsulation {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 f.write_str(self.token())
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
75pub enum StreamMethod {
76 #[serde(rename = "verifiable-segment-info")]
82 VerifiableSegmentInfo,
83 #[serde(rename = "per-segment")]
86 PerSegment,
87}
88
89impl StreamMethod {
90 pub const fn token(self) -> &'static str {
92 self.kernel().token()
93 }
94
95 pub fn from_token(token: &str) -> Option<Self> {
97 KernelMethod::from_token(token).map(|value| match value {
98 KernelMethod::VerifiableSegmentInfo => Self::VerifiableSegmentInfo,
99 KernelMethod::PerSegment => Self::PerSegment,
100 })
101 }
102
103 const fn kernel(self) -> KernelMethod {
104 match self {
105 Self::VerifiableSegmentInfo => KernelMethod::VerifiableSegmentInfo,
106 Self::PerSegment => KernelMethod::PerSegment,
107 }
108 }
109}
110
111impl std::fmt::Display for StreamMethod {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 f.write_str(self.token())
114 }
115}
116
117#[derive(Debug, Clone, Serialize)]
119pub struct SegmentReport {
120 pub sequence_number: usize,
122 pub manifest_label: String,
124 pub previous_manifest_label: Option<String>,
126 pub report: VerificationReport,
128}
129
130#[derive(Debug, Clone, Serialize)]
136pub struct StreamVerificationReport {
137 pub schema_version: String,
139 pub encapsulation: StreamEncapsulation,
141 pub method: StreamMethod,
143 pub integrity: String,
149 pub stream: Option<VerificationReport>,
151 pub segments: Vec<SegmentReport>,
153 pub chain_valid: Option<bool>,
157 pub chain_failures: Vec<String>,
159 pub network: NetworkReport,
161}
162
163impl StreamVerificationReport {
164 pub fn to_json(&self) -> Result<String, Error> {
166 serde_json::to_string(self).map_err(Error::Serialize)
167 }
168
169 pub fn to_pretty_json(&self) -> Result<String, Error> {
171 serde_json::to_string_pretty(self).map_err(Error::Serialize)
172 }
173}
174
175pub fn verify_stream(
178 init_segment: &[u8],
179 segments: &[&[u8]],
180 mime_type: &str,
181 encapsulation: StreamEncapsulation,
182 method: StreamMethod,
183) -> Result<StreamVerificationReport, Error> {
184 verify_stream_with_options(
185 init_segment,
186 segments,
187 mime_type,
188 encapsulation,
189 method,
190 &VerifyOptions::default(),
191 )
192}
193
194pub fn verify_stream_with_options(
217 init_segment: &[u8],
218 segments: &[&[u8]],
219 mime_type: &str,
220 encapsulation: StreamEncapsulation,
221 method: StreamMethod,
222 options: &VerifyOptions,
223) -> Result<StreamVerificationReport, Error> {
224 verify_stream_with_options_and_expected_seeks(
225 init_segment,
226 segments,
227 &options.expected_seek_positions,
228 mime_type,
229 encapsulation,
230 method,
231 options,
232 )
233}
234
235#[allow(clippy::too_many_arguments)]
236fn verify_stream_with_options_and_expected_seeks(
237 init_segment: &[u8],
238 segments: &[&[u8]],
239 expected_seek_positions: &[usize],
240 mime_type: &str,
241 encapsulation: StreamEncapsulation,
242 method: StreamMethod,
243 options: &VerifyOptions,
244) -> Result<StreamVerificationReport, Error> {
245 let mime = resolve_mime(mime_type, crate::c2pa_core::EngineProfile::GENEROUS)?;
248 if crate::c2pa_formats::AssetFormat::from_canonical_mime(&mime)
249 != Some(crate::c2pa_formats::AssetFormat::Bmff)
250 {
251 return Err(Error::UnsupportedMime(mime));
252 }
253 let presentation_times =
254 parse_stream_presentation_times(options.stream_presentation_times.as_deref())?;
255 if let Some(times) = &presentation_times {
256 if times.len() != segments.len() {
257 return Err(Error::InvalidValidationTime(format!(
258 "stream_presentation_times: {} entries for {} supplied segment(s); \
259 supply exactly one trusted presentation time per segment",
260 times.len(),
261 segments.len()
262 )));
263 }
264 }
265 let (mut report, needs) = stream_pass(
266 init_segment,
267 segments,
268 presentation_times.as_deref(),
269 expected_seek_positions,
270 &mime,
271 encapsulation,
272 method,
273 options,
274 )?;
275 let (network, evidence) = online::gather(options, &needs);
276 if evidence.is_empty() {
277 report.network = network;
278 return Ok(report);
279 }
280 let next = online::apply_evidence(options, &evidence);
283 let (mut report, _) = stream_pass(
284 init_segment,
285 segments,
286 presentation_times.as_deref(),
287 expected_seek_positions,
288 &mime,
289 encapsulation,
290 method,
291 &next,
292 )?;
293 report.network = network;
294 Ok(report)
295}
296
297#[allow(clippy::too_many_arguments)]
299fn stream_pass(
300 init_segment: &[u8],
301 segments: &[&[u8]],
302 presentation_times: Option<&[OffsetDateTime]>,
303 expected_seek_positions: &[usize],
304 mime: &str,
305 encapsulation: StreamEncapsulation,
306 method: StreamMethod,
307 options: &VerifyOptions,
308) -> Result<(StreamVerificationReport, Vec<NetworkNeed>), Error> {
309 let mime = mime.to_string();
310 let resolved = ResolvedOptions::resolve(options)?;
311 let output = verify_stream_safe_with_expected_seeks(
312 &resolved.input(init_segment, &mime),
313 init_segment,
314 segments,
315 presentation_times,
316 expected_seek_positions,
317 encapsulation.kernel(),
318 method.kernel(),
319 resolved.cawg_trust(),
320 resolved.cawg_allowed_certs(),
321 resolved.cawg_document_signing_require_anchor,
322 options.cawg_did_documents.as_ref(),
323 options.cawg_ica_trusted_issuers.as_deref(),
324 options.cawg_ica_trust_anchors.as_deref(),
325 options.cawg_ica_status_lists.as_ref(),
326 )
327 .map_err(map_validate_error)?;
328
329 Ok(match output {
330 StreamVerifyOutput::VerifiableSegmentInfo(stream) => {
331 let needs = online::network_needs(&stream);
332 let report = report_from_output(*stream, mime, &resolved);
333 (
334 StreamVerificationReport {
335 schema_version: REPORT_SCHEMA_VERSION.to_string(),
336 encapsulation,
337 method,
338 integrity: report.integrity.clone(),
339 stream: Some(report),
340 segments: Vec::new(),
341 chain_valid: None,
342 chain_failures: Vec::new(),
343 network: NetworkReport::default(),
344 },
345 needs,
346 )
347 }
348 StreamVerifyOutput::PerSegment(per_segment) => {
349 let mut needs = Vec::new();
350 let segments: Vec<SegmentReport> = per_segment
351 .segments
352 .into_iter()
353 .map(|segment| {
354 needs.extend(online::network_needs(&segment.output));
355 SegmentReport {
356 sequence_number: segment.sequence_number,
357 manifest_label: segment.manifest_label,
358 previous_manifest_label: segment.previous_manifest_label,
359 report: report_from_output(segment.output, mime.clone(), &resolved),
360 }
361 })
362 .collect();
363 let integrity = if per_segment.chain_valid
367 && segments
368 .iter()
369 .all(|segment| segment.report.integrity == "valid")
370 {
371 "valid"
372 } else {
373 "invalid"
374 };
375 (
376 StreamVerificationReport {
377 schema_version: REPORT_SCHEMA_VERSION.to_string(),
378 encapsulation,
379 method,
380 integrity: integrity.to_string(),
381 stream: None,
382 segments,
383 chain_valid: Some(per_segment.chain_valid),
384 chain_failures: per_segment.chain_failures,
385 network: NetworkReport::default(),
386 },
387 needs,
388 )
389 }
390 })
391}