Skip to main content

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>;