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 caller supplied a location outside the reader's source window.
64 InvalidLocation,
65 /// A timestamp token has no signer or conflicting multiple signer values.
66 SignerCount,
67 /// Timestamp information was detached instead of carried in the token.
68 DetachedContent,
69 /// An additional timestamp signature was retained for readable inspection.
70 ExtraSignature,
71}
72
73#[derive(Debug)]
74enum Detail {
75 Schema { code: ErrorCode, offset: u64 },
76 Encoding(structured_asn1::Error),
77 Pkix(structured_pkix::Error),
78}
79
80/// A source-located failure with independently retained diagnostic storage.
81#[derive(Debug)]
82pub struct Error {
83 field: &'static str,
84 detail: Detail,
85}
86
87impl Error {
88 pub(crate) fn schema(code: ErrorCode, field: &'static str, offset: u64) -> Self {
89 Self {
90 field,
91 detail: Detail::Schema { code, offset },
92 }
93 }
94
95 pub(crate) fn encoding(source: structured_asn1::Error, field: &'static str) -> Self {
96 Self {
97 field,
98 detail: Detail::Encoding(source),
99 }
100 }
101
102 pub(crate) fn pkix(source: structured_pkix::Error) -> Self {
103 Self {
104 field: source.field(),
105 detail: Detail::Pkix(source),
106 }
107 }
108
109 pub(crate) fn resource(reason: MemoryError, field: &'static str, offset: u64) -> Self {
110 Self::encoding(structured_asn1::Error::resource(reason, offset), field)
111 }
112
113 /// The processing failure category.
114 #[must_use]
115 pub fn code(&self) -> ErrorCode {
116 match &self.detail {
117 Detail::Schema { code, .. } => *code,
118 Detail::Encoding(source) => ErrorCode::Encoding(source.code()),
119 Detail::Pkix(source) => ErrorCode::Pkix(source.code()),
120 }
121 }
122
123 /// The schema field being processed.
124 #[must_use]
125 pub fn field(&self) -> &'static str {
126 self.field
127 }
128
129 /// The original source position where processing refused.
130 #[must_use]
131 pub fn offset(&self) -> u64 {
132 match &self.detail {
133 Detail::Schema { offset, .. } => *offset,
134 Detail::Encoding(source) => source.offset(),
135 Detail::Pkix(source) => source.offset(),
136 }
137 }
138
139 /// The original reader diagnostic, including any available path.
140 #[must_use]
141 pub fn encoding_source(&self) -> Option<&structured_asn1::Error> {
142 match &self.detail {
143 Detail::Encoding(source) => Some(source),
144 _ => None,
145 }
146 }
147
148 /// Whether processing refused resources rather than document syntax.
149 #[must_use]
150 pub fn is_resource(&self) -> bool {
151 match &self.detail {
152 Detail::Encoding(source) => source.is_resource(),
153 Detail::Pkix(source) => source.is_resource(),
154 Detail::Schema { .. } => false,
155 }
156 }
157
158 /// An inspection recovery must abort instead of retaining a document fault.
159 /// Resource refusals and a foreign producing reader are operation failures;
160 /// neither can establish a malformed value, mismatch or successful absence.
161 #[must_use]
162 pub fn is_terminal(&self) -> bool {
163 self.is_resource()
164 || self.code() == ErrorCode::Pkix(structured_pkix::ErrorCode::ForeignReader)
165 || self.code() == ErrorCode::InvalidLocation
166 }
167}
168
169impl fmt::Display for Error {
170 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
171 write!(
172 formatter,
173 "{} at {}: {:?}",
174 self.field,
175 self.offset(),
176 self.code()
177 )
178 }
179}
180
181#[cfg(feature = "std")]
182impl std::error::Error for Error {
183 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
184 match &self.detail {
185 Detail::Encoding(source) => Some(source),
186 Detail::Pkix(source) => Some(source),
187 Detail::Schema { .. } => None,
188 }
189 }
190}
191
192/// A fallible CMS processing operation.
193pub type Result<T> = core::result::Result<T, Error>;