1use serde::{Deserialize, Serialize};
16
17use crate::c2pa_formats::{
18 StreamEncapsulation as KernelEncapsulation, StreamMethod as KernelMethod,
19};
20use crate::c2pa_validate::stream::{verify_stream_safe_with_expected_seeks, StreamVerifyOutput};
21use crate::online::{self, NetworkNeed};
22use crate::{
23 map_validate_error, report_from_output, resolve_mime, Error, NetworkReport, ResolvedOptions,
24 VerificationReport, VerifyOptions, REPORT_SCHEMA_VERSION,
25};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub enum StreamEncapsulation {
35 #[serde(rename = "fMP4")]
38 Fmp4,
39 #[serde(rename = "CMAF")]
41 Cmaf,
42}
43
44impl StreamEncapsulation {
45 pub const fn token(self) -> &'static str {
47 self.kernel().token()
48 }
49
50 pub fn from_token(token: &str) -> Option<Self> {
52 KernelEncapsulation::from_token(token).map(|value| match value {
53 KernelEncapsulation::Fmp4 => Self::Fmp4,
54 KernelEncapsulation::Cmaf => Self::Cmaf,
55 })
56 }
57
58 const fn kernel(self) -> KernelEncapsulation {
59 match self {
60 Self::Fmp4 => KernelEncapsulation::Fmp4,
61 Self::Cmaf => KernelEncapsulation::Cmaf,
62 }
63 }
64}
65
66impl std::fmt::Display for StreamEncapsulation {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 f.write_str(self.token())
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
74pub enum StreamMethod {
75 #[serde(rename = "verifiable-segment-info")]
81 VerifiableSegmentInfo,
82 #[serde(rename = "per-segment")]
85 PerSegment,
86}
87
88impl StreamMethod {
89 pub const fn token(self) -> &'static str {
91 self.kernel().token()
92 }
93
94 pub fn from_token(token: &str) -> Option<Self> {
96 KernelMethod::from_token(token).map(|value| match value {
97 KernelMethod::VerifiableSegmentInfo => Self::VerifiableSegmentInfo,
98 KernelMethod::PerSegment => Self::PerSegment,
99 })
100 }
101
102 const fn kernel(self) -> KernelMethod {
103 match self {
104 Self::VerifiableSegmentInfo => KernelMethod::VerifiableSegmentInfo,
105 Self::PerSegment => KernelMethod::PerSegment,
106 }
107 }
108}
109
110impl std::fmt::Display for StreamMethod {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.write_str(self.token())
113 }
114}
115
116#[derive(Debug, Clone, Serialize)]
118pub struct SegmentReport {
119 pub sequence_number: usize,
121 pub manifest_label: String,
123 pub previous_manifest_label: Option<String>,
125 pub report: VerificationReport,
127}
128
129#[derive(Debug, Clone, Serialize)]
135pub struct StreamVerificationReport {
136 pub schema_version: String,
138 pub encapsulation: StreamEncapsulation,
140 pub method: StreamMethod,
142 pub integrity: String,
148 pub stream: Option<VerificationReport>,
150 pub segments: Vec<SegmentReport>,
152 pub chain_valid: Option<bool>,
156 pub chain_failures: Vec<String>,
158 pub network: NetworkReport,
160}
161
162impl StreamVerificationReport {
163 pub fn to_json(&self) -> Result<String, Error> {
165 serde_json::to_string(self).map_err(Error::Serialize)
166 }
167
168 pub fn to_pretty_json(&self) -> Result<String, Error> {
170 serde_json::to_string_pretty(self).map_err(Error::Serialize)
171 }
172}
173
174pub fn verify_stream(
177 init_segment: &[u8],
178 segments: &[&[u8]],
179 mime_type: &str,
180 encapsulation: StreamEncapsulation,
181 method: StreamMethod,
182) -> Result<StreamVerificationReport, Error> {
183 verify_stream_with_options(
184 init_segment,
185 segments,
186 mime_type,
187 encapsulation,
188 method,
189 &VerifyOptions::default(),
190 )
191}
192
193pub fn verify_stream_with_options(
210 init_segment: &[u8],
211 segments: &[&[u8]],
212 mime_type: &str,
213 encapsulation: StreamEncapsulation,
214 method: StreamMethod,
215 options: &VerifyOptions,
216) -> Result<StreamVerificationReport, Error> {
217 verify_stream_with_options_and_expected_seeks(
218 init_segment,
219 segments,
220 &options.expected_seek_positions,
221 mime_type,
222 encapsulation,
223 method,
224 options,
225 )
226}
227
228#[allow(clippy::too_many_arguments)]
229fn verify_stream_with_options_and_expected_seeks(
230 init_segment: &[u8],
231 segments: &[&[u8]],
232 expected_seek_positions: &[usize],
233 mime_type: &str,
234 encapsulation: StreamEncapsulation,
235 method: StreamMethod,
236 options: &VerifyOptions,
237) -> Result<StreamVerificationReport, Error> {
238 let mime = resolve_mime(mime_type)?;
239 if crate::c2pa_formats::AssetFormat::from_mime(&mime)
240 != Some(crate::c2pa_formats::AssetFormat::Bmff)
241 {
242 return Err(Error::UnsupportedMime(mime));
243 }
244 let (mut report, needs) = stream_pass(
245 init_segment,
246 segments,
247 expected_seek_positions,
248 &mime,
249 encapsulation,
250 method,
251 options,
252 )?;
253 let (network, evidence) = online::gather(options, &needs);
254 if evidence.is_empty() {
255 report.network = network;
256 return Ok(report);
257 }
258 let next = online::apply_evidence(options, &evidence);
261 let (mut report, _) = stream_pass(
262 init_segment,
263 segments,
264 expected_seek_positions,
265 &mime,
266 encapsulation,
267 method,
268 &next,
269 )?;
270 report.network = network;
271 Ok(report)
272}
273
274#[allow(clippy::too_many_arguments)]
276fn stream_pass(
277 init_segment: &[u8],
278 segments: &[&[u8]],
279 expected_seek_positions: &[usize],
280 mime: &str,
281 encapsulation: StreamEncapsulation,
282 method: StreamMethod,
283 options: &VerifyOptions,
284) -> Result<(StreamVerificationReport, Vec<NetworkNeed>), Error> {
285 let mime = mime.to_string();
286 let resolved = ResolvedOptions::resolve(options)?;
287 let output = verify_stream_safe_with_expected_seeks(
288 &resolved.input(init_segment, &mime),
289 init_segment,
290 segments,
291 expected_seek_positions,
292 encapsulation.kernel(),
293 method.kernel(),
294 resolved.cawg_trust(),
295 resolved.cawg_allowed_certs(),
296 true,
297 options.cawg_did_documents.as_ref(),
298 options.cawg_ica_trusted_issuers.as_deref(),
299 options.cawg_ica_trust_anchors.as_deref(),
300 options.cawg_ica_status_lists.as_ref(),
301 )
302 .map_err(map_validate_error)?;
303
304 Ok(match output {
305 StreamVerifyOutput::VerifiableSegmentInfo(stream) => {
306 let needs = online::network_needs(&stream);
307 let report = report_from_output(*stream, mime, &resolved);
308 (
309 StreamVerificationReport {
310 schema_version: REPORT_SCHEMA_VERSION.to_string(),
311 encapsulation,
312 method,
313 integrity: report.integrity.clone(),
314 stream: Some(report),
315 segments: Vec::new(),
316 chain_valid: None,
317 chain_failures: Vec::new(),
318 network: NetworkReport::default(),
319 },
320 needs,
321 )
322 }
323 StreamVerifyOutput::PerSegment(per_segment) => {
324 let mut needs = Vec::new();
325 let segments: Vec<SegmentReport> = per_segment
326 .segments
327 .into_iter()
328 .map(|segment| {
329 needs.extend(online::network_needs(&segment.output));
330 SegmentReport {
331 sequence_number: segment.sequence_number,
332 manifest_label: segment.manifest_label,
333 previous_manifest_label: segment.previous_manifest_label,
334 report: report_from_output(segment.output, mime.clone(), &resolved),
335 }
336 })
337 .collect();
338 let integrity = if per_segment.chain_valid
342 && segments
343 .iter()
344 .all(|segment| segment.report.integrity == "valid")
345 {
346 "valid"
347 } else {
348 "invalid"
349 };
350 (
351 StreamVerificationReport {
352 schema_version: REPORT_SCHEMA_VERSION.to_string(),
353 encapsulation,
354 method,
355 integrity: integrity.to_string(),
356 stream: None,
357 segments,
358 chain_valid: Some(per_segment.chain_valid),
359 chain_failures: per_segment.chain_failures,
360 network: NetworkReport::default(),
361 },
362 needs,
363 )
364 }
365 })
366}