structured_cms/error.rs
1//! Located processing failures, retaining the original ASN.1 diagnostic.
2
3use core::fmt;
4use structured_asn1::memory::MemoryError;
5
6/// A processing failure, distinct from a provider's signature answer.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ErrorCode {
9 /// The producing ASN.1 reader refused the encoding or its resources.
10 Encoding(structured_asn1::ErrorCode),
11 /// A shared PKIX semantic or conformance mechanism refused.
12 Pkix(structured_pkix::ErrorCode),
13 /// An element has the wrong tag for the selected schema field.
14 UnexpectedTag,
15 /// The logical content length cannot be represented.
16 LengthOverflow,
17 /// A required schema component was not present.
18 MissingField,
19 /// A container includes an additional component beyond its schema.
20 TrailingData,
21 /// A required nonempty attribute collection is empty.
22 EmptyAttributes,
23 /// An attribute that requires one value holds none or several.
24 AttributeCardinality,
25 /// Signed attribute encodings are not in DER SET OF order.
26 AttributeOrder,
27 /// The numeric schema version does not fit its representation.
28 ValueTooLarge,
29 /// The declared version disagrees with the encoded schema choices.
30 VersionMismatch,
31 /// `ContentInfo` selected a type other than the requested protocol schema.
32 UnexpectedContentType,
33 /// A DER SET OF catalogue is not ordered by original encodings.
34 SetOrder,
35 /// The content-type attribute disagrees with its enclosing signed content.
36 ContentTypeMismatch,
37 /// A recognized attribute is in a placement its protocol forbids.
38 AttributePlacement,
39 /// A recognized attribute value violates its selected type or profile syntax.
40 AttributeSyntax,
41 /// A fixed digest identity cannot produce the declared message-digest width.
42 DigestWidth,
43 /// The selected algorithm's identifier violates its own parameter syntax.
44 AlgorithmSyntax,
45 /// Protected algorithms disagree under the selected provider's full semantics.
46 AlgorithmProtection,
47 /// A request uses another provider, or lacks a stable selection owner.
48 ProviderMismatch,
49 /// A readable schema default was explicitly encoded where DER omits it.
50 DefaultWritten,
51 /// A bounded schema container has an unrecognized field.
52 UnknownField,
53 /// A field is repeated; readable identical recovery retains a fault.
54 DuplicateField,
55 /// Recognized fields were written outside their required SEQUENCE order.
56 FieldOrder,
57 /// A readable value violates the selected schema's scalar bounds.
58 MalformedValue,
59 /// The written protocol version has no implemented schema interpretation.
60 UnsupportedVersion,
61 /// A caller supplied a location outside the reader's source window.
62 InvalidLocation,
63 /// A timestamp token has no signer or conflicting multiple signer values.
64 SignerCount,
65 /// Timestamp information was detached instead of carried in the token.
66 DetachedContent,
67 /// An additional timestamp signature was retained for readable inspection.
68 ExtraSignature,
69}
70
71#[derive(Debug)]
72enum Detail {
73 Schema { code: ErrorCode, offset: u64 },
74 Encoding(structured_asn1::Error),
75 Pkix(structured_pkix::Error),
76}
77
78/// A source-located failure with independently retained diagnostic storage.
79#[derive(Debug)]
80pub struct Error {
81 field: &'static str,
82 detail: Detail,
83}
84
85impl Error {
86 pub(crate) fn schema(code: ErrorCode, field: &'static str, offset: u64) -> Self {
87 Self {
88 field,
89 detail: Detail::Schema { code, offset },
90 }
91 }
92
93 pub(crate) fn encoding(source: structured_asn1::Error, field: &'static str) -> Self {
94 Self {
95 field,
96 detail: Detail::Encoding(source),
97 }
98 }
99
100 pub(crate) fn pkix(source: structured_pkix::Error) -> Self {
101 Self {
102 field: source.field(),
103 detail: Detail::Pkix(source),
104 }
105 }
106
107 pub(crate) fn resource(reason: MemoryError, field: &'static str, offset: u64) -> Self {
108 Self::encoding(structured_asn1::Error::resource(reason, offset), field)
109 }
110
111 /// The processing failure category.
112 #[must_use]
113 pub fn code(&self) -> ErrorCode {
114 match &self.detail {
115 Detail::Schema { code, .. } => *code,
116 Detail::Encoding(source) => ErrorCode::Encoding(source.code()),
117 Detail::Pkix(source) => ErrorCode::Pkix(source.code()),
118 }
119 }
120
121 /// The schema field being processed.
122 #[must_use]
123 pub fn field(&self) -> &'static str {
124 self.field
125 }
126
127 /// The original source position where processing refused.
128 #[must_use]
129 pub fn offset(&self) -> u64 {
130 match &self.detail {
131 Detail::Schema { offset, .. } => *offset,
132 Detail::Encoding(source) => source.offset(),
133 Detail::Pkix(source) => source.offset(),
134 }
135 }
136
137 /// The original reader diagnostic, including any available path.
138 #[must_use]
139 pub fn encoding_source(&self) -> Option<&structured_asn1::Error> {
140 match &self.detail {
141 Detail::Encoding(source) => Some(source),
142 _ => None,
143 }
144 }
145
146 /// Whether processing refused resources rather than document syntax.
147 #[must_use]
148 pub fn is_resource(&self) -> bool {
149 match &self.detail {
150 Detail::Encoding(source) => source.is_resource(),
151 Detail::Pkix(source) => source.is_resource(),
152 Detail::Schema { .. } => false,
153 }
154 }
155
156 /// An inspection recovery must abort instead of retaining a document fault.
157 /// Resource refusals and a foreign producing reader are operation failures;
158 /// neither can establish a malformed value, mismatch or successful absence.
159 #[must_use]
160 pub fn is_terminal(&self) -> bool {
161 self.is_resource()
162 || self.code() == ErrorCode::Pkix(structured_pkix::ErrorCode::ForeignReader)
163 || self.code() == ErrorCode::InvalidLocation
164 }
165}
166
167impl fmt::Display for Error {
168 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(
170 formatter,
171 "{} at {}: {:?}",
172 self.field,
173 self.offset(),
174 self.code()
175 )
176 }
177}
178
179#[cfg(feature = "std")]
180impl std::error::Error for Error {
181 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
182 match &self.detail {
183 Detail::Encoding(source) => Some(source),
184 Detail::Pkix(source) => Some(source),
185 Detail::Schema { .. } => None,
186 }
187 }
188}
189
190/// A fallible CMS processing operation.
191pub type Result<T> = core::result::Result<T, Error>;