structured_asn1/tag.rs
1//! Identifier octets: the class, whether the element is constructed, and the
2//! number.
3
4use crate::error::ErrorCode;
5
6/// The two bits that say who defines the tag number's meaning.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Class {
9 /// Defined by X.680 itself: INTEGER, SEQUENCE and the rest.
10 Universal,
11 /// Defined by the application.
12 Application,
13 /// Defined by the position within the enclosing type: `[0]`, `[1]`.
14 ContextSpecific,
15 /// Defined privately.
16 Private,
17}
18
19impl Class {
20 fn from_bits(bits: u8) -> Self {
21 match bits >> 6 {
22 0 => Self::Universal,
23 1 => Self::Application,
24 2 => Self::ContextSpecific,
25 _ => Self::Private,
26 }
27 }
28}
29
30/// Universal tag numbers this parser names. Others are read as numbers.
31pub mod universal {
32 /// BOOLEAN.
33 pub const BOOLEAN: u32 = 1;
34 /// INTEGER.
35 pub const INTEGER: u32 = 2;
36 /// BIT STRING.
37 pub const BIT_STRING: u32 = 3;
38 /// OCTET STRING.
39 pub const OCTET_STRING: u32 = 4;
40 /// NULL.
41 pub const NULL: u32 = 5;
42 /// OBJECT IDENTIFIER.
43 pub const OID: u32 = 6;
44 /// `UTF8String`.
45 pub const UTF8_STRING: u32 = 12;
46 /// SEQUENCE and SEQUENCE OF.
47 pub const SEQUENCE: u32 = 16;
48 /// SET and SET OF.
49 pub const SET: u32 = 17;
50 /// `PrintableString`.
51 pub const PRINTABLE_STRING: u32 = 19;
52 /// `IA5String`.
53 pub const IA5_STRING: u32 = 22;
54 /// `UTCTime`.
55 pub const UTC_TIME: u32 = 23;
56 /// `GeneralizedTime`.
57 pub const GENERALIZED_TIME: u32 = 24;
58}
59
60/// One element's identity.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct Tag {
63 class: Class,
64 constructed: bool,
65 /// The number, or `None` for one wider than this reader names. The
66 /// encoding bounds no tag number (X.690 8.1.2.4.2), and a schema assigns
67 /// small ones, so a wide number is not one anything names: it is read
68 /// and kept apart from every named tag rather than refused, because
69 /// refusing it would block a certificate over an element nothing was
70 /// going to interpret anyway.
71 ///
72 /// Every wide number is the same `None` here, so two tags that are both
73 /// beyond naming are equal, which is what the accessor says about them.
74 /// What tells two documents apart is their bytes.
75 number: Option<u32>,
76}
77
78impl Tag {
79 /// A tag built from its parts.
80 #[must_use]
81 pub fn new(class: Class, constructed: bool, number: u32) -> Self {
82 Self {
83 class,
84 constructed,
85 number: Some(number),
86 }
87 }
88
89 /// A tag whose number is wider than this reader names.
90 ///
91 /// Every such tag is the same tag here, which is what its class and
92 /// construction say and nothing more. Two documents writing different
93 /// wide numbers are still told apart by their bytes, which is what
94 /// identity is compared on; what this decides is only that neither
95 /// matches a tag a schema names.
96 #[must_use]
97 pub fn beyond_naming(class: Class, constructed: bool) -> Self {
98 Self {
99 class,
100 constructed,
101 number: None,
102 }
103 }
104
105 /// A universal primitive tag, the common case for values.
106 #[must_use]
107 pub fn primitive(number: u32) -> Self {
108 Self::new(Class::Universal, false, number)
109 }
110
111 /// A universal constructed tag, the common case for containers.
112 #[must_use]
113 pub fn constructed(number: u32) -> Self {
114 Self::new(Class::Universal, true, number)
115 }
116
117 /// A context-specific tag, as written `[n]` in a module.
118 #[must_use]
119 pub fn context(constructed: bool, number: u32) -> Self {
120 Self::new(Class::ContextSpecific, constructed, number)
121 }
122
123 /// Who defines what this number means.
124 #[must_use]
125 pub fn class(self) -> Class {
126 self.class
127 }
128
129 /// Whether the contents are further elements rather than a value.
130 #[must_use]
131 pub fn is_constructed(self) -> bool {
132 self.constructed
133 }
134
135 /// The number within the class, where it is one this reader names.
136 ///
137 /// `None` is a number too wide for that, which no schema assigns and
138 /// nothing here interprets. A caller matching against the numbers it
139 /// knows gets no match, which is the answer.
140 #[must_use]
141 pub fn number(self) -> Option<u32> {
142 self.number
143 }
144
145 /// Whether this is the end-of-contents marker that closes an
146 /// indefinite-length element.
147 #[must_use]
148 pub fn is_end_of_contents(self) -> bool {
149 self.class == Class::Universal && !self.constructed && self.number == Some(0)
150 }
151}
152
153/// Reads the identifier octets at the front of `input`.
154///
155/// Returns the tag and how many bytes it took. The high-tag-number form is
156/// accepted, and two spellings of it are refused: a first byte of `0x80`,
157/// which pads the number with a leading zero, and the long form used for a
158/// number the short one already writes. Both are ways to write the same
159/// number in different bytes, which is how two parsers are made to disagree
160/// about what they read.
161///
162/// A number wider than the reader names is not one of those: it is read, and
163/// kept apart from every named tag.
164pub(crate) fn read_accounted(
165 input: &[u8],
166 offset: u64,
167 mut charge: impl FnMut(u64) -> Result<(), ErrorCode>,
168) -> Result<(Tag, usize), (ErrorCode, u64)> {
169 let fail = |code| (code, offset);
170
171 charge(1).map_err(fail)?;
172
173 let first = *input
174 .first()
175 .ok_or_else(|| fail(ErrorCode::UnexpectedEnd))?;
176 let class = Class::from_bits(first);
177 let constructed = first & 0x20 != 0;
178 let low = first & 0x1f;
179
180 if low != 0x1f {
181 // Universal zero belongs to the end-of-contents marker, which is
182 // primitive. Constructed, it is neither that marker nor a tag any
183 // encoding defines, and returning it hands the reader an element that
184 // does not exist.
185 if class == Class::Universal && low == 0 && constructed {
186 return Err(fail(ErrorCode::MalformedTag));
187 }
188 return Ok((Tag::new(class, constructed, u32::from(low)), 1));
189 }
190
191 // The number, where it fits, and `None` for one wider than that. The
192 // encoding bounds no tag number (X.690 8.1.2.4.2), and a schema assigns
193 // small ones, so a wide number is not a tag anything names; refusing it
194 // would block a certificate over an element nothing was going to
195 // interpret. What is still refused is a header spending more bytes on
196 // the number than any document has reason to, which is a limit on the
197 // size of the header rather than on the size of the number.
198 let mut number: Option<u64> = Some(0);
199 let mut taken = 1;
200 loop {
201 charge(1).map_err(fail)?;
202 let byte = *input
203 .get(taken)
204 .ok_or_else(|| fail(ErrorCode::UnexpectedEnd))?;
205 taken += 1;
206
207 // A first continuation byte of 0x80 means the number starts with a zero
208 // seven-bit group, which is a longer spelling of a shorter number.
209 if taken == 2 && byte == 0x80 {
210 return Err(fail(ErrorCode::MalformedTag));
211 }
212
213 number =
214 number.and_then(|value| value.checked_mul(128)?.checked_add(u64::from(byte & 0x7f)));
215
216 if byte & 0x80 == 0 {
217 break;
218 }
219
220 // Continuing past this would read the rest of the document looking
221 // for a byte without the high bit set.
222 if taken > TAG_NUMBER_BYTES {
223 return Err(fail(ErrorCode::TagTooLarge));
224 }
225 }
226
227 // The high-tag-number form is only for numbers that do not fit the low form.
228 if number.is_some_and(|number| number < 0x1f) {
229 return Err(fail(ErrorCode::MalformedTag));
230 }
231
232 let tag = match number.and_then(|number| u32::try_from(number).ok()) {
233 Some(number) => Tag::new(class, constructed, number),
234 None => Tag::beyond_naming(class, constructed),
235 };
236 Ok((tag, taken))
237}
238
239#[cfg(test)]
240fn read(input: &[u8], offset: u64) -> Result<(Tag, usize), (ErrorCode, u64)> {
241 read_accounted(input, offset, |_| Ok(()))
242}
243
244/// How many bytes a tag's number may be written in.
245///
246/// Not a bound on the number, which the encoding does not bound: a number
247/// wider than this reader names is read and kept apart from the named ones.
248/// This bounds the header instead. Without it a run of continuation bytes
249/// would be followed to the end of the document, so it is the same kind of
250/// limit as the one on how deeply elements may nest, and sixteen bytes is
251/// several times what any assigned number has ever needed.
252const TAG_NUMBER_BYTES: usize = 16;
253
254#[cfg(test)]
255mod tests;