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