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