1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use std::fmt;
#[derive(Debug)]
pub enum Error {
/// No `C2PA` table is present in the font.
NotFound,
/// The font is not a supported SFNT (TrueType/OpenType) file.
NotSfnt,
/// Font collections (`ttcf`) are not supported.
Collection,
/// WOFF/WOFF2 wrapped fonts are not supported; decompress to SFNT first.
Woff,
/// The SFNT structure could not be parsed.
InvalidFont(String),
/// The `C2PA` table could not be parsed or violates the spec.
InvalidTable(String),
/// Hard-binding or delegated (c2pa-rs) validation failed.
Validation(String),
Io(std::io::Error),
}
impl Error {
/// The registered C2PA validation status code for this error, or `None`
/// when the condition carries no status code.
///
/// The specification defines no font-specific codes. [`Error::NotFound`]
/// means the font carries no provenance, which is not a failure, and the
/// parsing variants occur before any manifest is located.
///
/// [`Error::Validation`] wraps a failure reported by the delegated
/// validator, which carries its own status codes; those are surfaced in the
/// report from [`crate::validate`] rather than flattened into this one
/// string.
///
/// Every crate in this family exposes this method, so a dispatcher handling
/// several embedding methods can ask the same question of any of them.
pub fn code(&self) -> Option<&'static str> {
None
}
/// Whether this error means the font carries no provenance at all, as
/// opposed to provenance that was found and rejected.
pub fn is_no_manifest_located(&self) -> bool {
matches!(self, Self::NotFound)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFound => write!(f, "no C2PA table found in font"),
Self::NotSfnt => write!(f, "not a supported SFNT (TrueType/OpenType) font"),
Self::Collection => write!(f, "font collections (ttcf) are not supported"),
Self::Woff => write!(
f,
"WOFF/WOFF2 fonts are not supported; decompress to SFNT first"
),
Self::InvalidFont(s) => write!(f, "invalid font: {s}"),
Self::InvalidTable(s) => write!(f, "invalid C2PA table: {s}"),
Self::Validation(s) => write!(f, "validation failed: {s}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
/// The specification defines no font-specific codes, so none may appear.
/// Guards against a later edit inventing one.
#[test]
fn no_variant_claims_a_status_code() {
for e in [
Error::NotFound,
Error::NotSfnt,
Error::Collection,
Error::Woff,
Error::InvalidFont("x".into()),
Error::InvalidTable("x".into()),
Error::Validation("x".into()),
Error::Io(std::io::Error::other("x")),
] {
assert_eq!(e.code(), None, "{e:?} claimed a status code");
}
}
/// A font with no C2PA table is unsigned; an unparseable one is a different
/// problem, and a delegated validation failure is different again.
#[test]
fn only_a_missing_table_means_unsigned() {
assert!(Error::NotFound.is_no_manifest_located());
for e in [
Error::NotSfnt,
Error::Woff,
Error::InvalidTable("x".into()),
Error::Validation("x".into()),
] {
assert!(!e.is_no_manifest_located(), "{e:?} misreported as unsigned");
}
}
}