structured_cms/timestamp/
token.rs1use super::{TokenInfo, record};
4use crate::content_source::{ContentError, SourceMap};
5use crate::{ContentInfo, Error, ErrorCode, Result, SignedData, oid};
6use structured_asn1::memory::{OwnedVec, Work};
7use structured_asn1::{Encoding, Parser, Reader};
8
9#[derive(Debug)]
15pub struct Token<'a> {
16 message: SignedData<'a>,
17 faults: OwnedVec<Error>,
18}
19
20#[derive(Debug)]
22pub enum InfoError<E> {
23 Processing(Error),
25 Callback(E),
27}
28
29impl<'a> Token<'a> {
30 pub fn read(parser: &'a Parser, document: &'a [u8], base: u64) -> Result<Self> {
33 let mut root = parser
34 .read(document, base)
35 .map_err(|error| Error::encoding(error, "contentInfo"))?;
36 let (envelope, parent) = ContentInfo::read(&mut root)?;
37 parent
38 .execution()
39 .charge_work(envelope.kind().as_bytes().len() as u64)
40 .map_err(|reason| {
41 Error::resource(reason, "contentInfo.contentType", envelope.span().start)
42 })?;
43 if envelope.kind() != oid::SIGNED_DATA {
44 return Err(Error::schema(
45 ErrorCode::UnexpectedContentType,
46 "contentInfo.contentType",
47 envelope.span().start,
48 ));
49 }
50 let message = SignedData::read_element(&parent, envelope.content())?;
51 Self::from_message(message, &parent)
52 }
53
54 pub fn from_message(message: SignedData<'a>, parent: &Reader<'a>) -> Result<Self> {
64 let field = "signedData.signerInfos";
65 let first = message
66 .signers()
67 .first()
68 .ok_or_else(|| Error::schema(ErrorCode::SignerCount, field, message.span().start))?;
69 first.require_session(parent, field)?;
70 let execution = parent.execution();
71 let mut faults = execution.vector();
72 let mut duplicate = None;
73 for extra in &message.signers()[1..] {
74 execution
75 .charge_work(1)
76 .map_err(|reason| Error::resource(reason, field, extra.span().start))?;
77 if !first.same_value(extra, parent)? {
78 return Err(Error::schema(
79 ErrorCode::SignerCount,
80 field,
81 extra.span().start,
82 ));
83 }
84 duplicate.get_or_insert(extra.span().start);
85 }
86 if let Some(at) = duplicate {
87 record(
88 &mut faults,
89 Error::schema(ErrorCode::ExtraSignature, field, at),
90 )?;
91 }
92 record_counters(first, parent, &mut faults)?;
93 let content = message.content();
94 execution
95 .charge_work(content.kind().as_bytes().len() as u64)
96 .map_err(|reason| {
97 Error::resource(
98 reason,
99 "signedData.encapContentInfo.eContentType",
100 content.span().start,
101 )
102 })?;
103 if content.kind() != oid::TST_INFO {
104 return Err(Error::schema(
105 ErrorCode::UnexpectedContentType,
106 "signedData.encapContentInfo.eContentType",
107 content.span().start,
108 ));
109 }
110 if content.content().is_none() {
111 return Err(Error::schema(
112 ErrorCode::DetachedContent,
113 "signedData.encapContentInfo.eContent",
114 content.span().start,
115 ));
116 }
117 Ok(Self {
118 message,
119 faults: faults.into_owned(),
120 })
121 }
122
123 #[must_use]
125 pub fn message(&self) -> &SignedData<'a> {
126 &self.message
127 }
128
129 #[must_use]
131 pub fn signer(&self) -> &crate::signer::SignerInfo<'a> {
132 &self.message.signers()[0]
133 }
134
135 #[must_use]
137 pub fn faults(&self) -> &[Error] {
138 &self.faults
139 }
140
141 #[expect(
152 clippy::result_large_err,
153 reason = "preserve both original cause and accounted source context without allocating an error envelope"
154 )]
155 pub fn with_info<R, E>(
156 &self,
157 parent: &Reader<'_>,
158 callback: impl for<'scope> FnOnce(
159 &TokenInfo<'scope>,
160 &Reader<'scope>,
161 &SourceMap,
162 ) -> core::result::Result<R, E>,
163 ) -> core::result::Result<R, ContentError<InfoError<E>>> {
164 self.inspect(parent, None, callback)
165 }
166
167 #[expect(
173 clippy::result_large_err,
174 reason = "retain original cause and accounted physical source context"
175 )]
176 pub fn with_info_mapped<R, E>(
177 &self,
178 parent: &Reader<'_>,
179 source: &SourceMap,
180 callback: impl for<'scope> FnOnce(
181 &TokenInfo<'scope>,
182 &Reader<'scope>,
183 &SourceMap,
184 ) -> core::result::Result<R, E>,
185 ) -> core::result::Result<R, ContentError<InfoError<E>>> {
186 self.inspect(parent, Some(source), callback)
187 }
188
189 #[expect(
190 clippy::result_large_err,
191 reason = "retain original cause and accounted physical source context"
192 )]
193 fn inspect<R, E>(
194 &self,
195 parent: &Reader<'_>,
196 source: Option<&SourceMap>,
197 callback: impl for<'scope> FnOnce(
198 &TokenInfo<'scope>,
199 &Reader<'scope>,
200 &SourceMap,
201 ) -> core::result::Result<R, E>,
202 ) -> core::result::Result<R, ContentError<InfoError<E>>> {
203 let Some(content) = self.message.content().content() else {
204 return Err(ContentError::Processing(Error::schema(
205 ErrorCode::DetachedContent,
206 "signedData.encapContentInfo.eContent",
207 self.message.content().span().start,
208 )));
209 };
210 let decode = |reader: &mut Reader<'_>, context: &SourceMap| {
211 let info = TokenInfo::read_carried(reader).map_err(InfoError::Processing)?;
212 callback(&info, reader, context).map_err(InfoError::Callback)
213 };
214 match source {
215 Some(source) => {
216 content.with_nested_reader_mapped(parent, source, Encoding::Ber, "tstInfo", decode)
217 }
218 None => content.with_nested_reader(parent, Encoding::Ber, "tstInfo", decode),
219 }
220 }
221}
222
223fn record_counters<W: Work>(
224 signer: &crate::signer::SignerInfo<'_>,
225 parent: &Reader<'_>,
226 faults: &mut OwnedVec<Error, W>,
227) -> Result<()> {
228 for (attributes, field) in [
233 (
234 signer.signed_attributes(),
235 "signerInfo.signedAttrs.countersignature",
236 ),
237 (
238 signer.unsigned_attributes(),
239 "signerInfo.unsignedAttrs.countersignature",
240 ),
241 ] {
242 let Some(attributes) = attributes else {
243 continue;
244 };
245 for attribute in attributes.all_in(oid::COUNTERSIGNATURE, parent) {
246 let attribute = attribute?;
247 record(
248 faults,
249 Error::schema(ErrorCode::ExtraSignature, field, attribute.span().start),
250 )?;
251 }
252 }
253 Ok(())
254}