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