Skip to main content

rustlavel_ldap/
ber.rs

1//! Just enough BER to speak LDAP.
2//!
3//! LDAP v3 is ASN.1 BER over a socket (RFC 4511 §5.1), so this has to exist.
4//! It is a serialisation format rather than cryptography, so rule one applies
5//! and it is written here — but the subset is deliberately small, because
6//! RFC 4511 already made it small: **only the definite form of length encoding
7//! is used**, every tag LDAP defines is below 31, and nothing in the protocol
8//! needs REAL, BIT STRING or a tag longer than one octet.
9//!
10//! Everything outside that subset is refused rather than parsed. A decoder
11//! that accepts more than the specification allows is a decoder that accepts
12//! things the specification was trying to keep out, and the bytes here arrive
13//! from a socket that an attacker on the path controls completely.
14//!
15//! What is refused, and why:
16//!
17//! * **Indefinite lengths.** RFC 4511 forbids them outright. A parser that
18//!   supports them has to scan for an end-of-contents marker, which is how a
19//!   message's real boundary and its declared boundary come apart.
20//! * **Multi-byte tags.** LDAP's highest tag number is 25. A high-tag-number
21//!   form is either a bug or a probe.
22//! * **A length longer than the bytes present.** Checked *before* anything is
23//!   allocated, so a four-byte header claiming four gigabytes costs nothing.
24//! * **Non-minimal lengths and integers.** X.690 requires the shortest form for
25//!   both. Two spellings of the same number is how two parsers end up
26//!   disagreeing about the same bytes.
27//! * **Nesting past [`MAX_DEPTH`].** A filter is a recursive type, so without a
28//!   limit a few dozen bytes of `(!(!(!(…))))` recurse until the stack gives
29//!   out.
30
31use rustlavel_core::{Error, Result};
32
33/// The class bits of an identifier octet.
34pub const UNIVERSAL: u8 = 0x00;
35/// Application-class tags: LDAP's protocol operations.
36pub const APPLICATION: u8 = 0x40;
37/// Context-class tags: the fields inside an operation, and filter choices.
38pub const CONTEXT: u8 = 0x80;
39/// The constructed bit — set when the contents are themselves elements.
40pub const CONSTRUCTED: u8 = 0x20;
41
42pub const BOOLEAN: u8 = 0x01;
43pub const INTEGER: u8 = 0x02;
44pub const OCTET_STRING: u8 = 0x04;
45pub const NULL: u8 = 0x05;
46pub const ENUMERATED: u8 = 0x0a;
47pub const SEQUENCE: u8 = 0x30;
48pub const SET: u8 = 0x31;
49
50/// The identifier octet for `[APPLICATION n]`.
51pub const fn application(number: u8, constructed: bool) -> u8 {
52    APPLICATION | if constructed { CONSTRUCTED } else { 0 } | number
53}
54
55/// The identifier octet for `[n]` — a context-specific tag.
56pub const fn context(number: u8, constructed: bool) -> u8 {
57    CONTEXT | if constructed { CONSTRUCTED } else { 0 } | number
58}
59
60/// How deep a nested structure may go.
61///
62/// A realistic search filter is three or four levels; the deepest thing a
63/// directory sends back is a search result entry at five. Anything approaching
64/// this is hostile, and `Filter` is recursive on both sides of the wire, so
65/// the limit is what stands between a short message and a blown stack.
66pub const MAX_DEPTH: usize = 32;
67
68// ---------------------------------------------------------------------------
69// Encoding
70// ---------------------------------------------------------------------------
71
72/// A BER element under construction.
73///
74/// Nested elements are built into a temporary encoder and copied in once their
75/// length is known. BER puts the length before the contents, so something has
76/// to give: either the bytes move, or the length does. An LDAP message is a few
77/// hundred bytes and a handful of levels deep, so the copy is not worth
78/// out-thinking, and building inside-out is the version a reader can check.
79#[derive(Default)]
80pub struct Encoder {
81    bytes: Vec<u8>,
82}
83
84impl Encoder {
85    pub fn new() -> Encoder {
86        Encoder::default()
87    }
88
89    pub fn as_bytes(&self) -> &[u8] {
90        &self.bytes
91    }
92
93    pub fn into_bytes(self) -> Vec<u8> {
94        self.bytes
95    }
96
97    pub fn len(&self) -> usize {
98        self.bytes.len()
99    }
100
101    pub fn is_empty(&self) -> bool {
102        self.bytes.is_empty()
103    }
104
105    /// One element: identifier octet, definite length, contents verbatim.
106    pub fn element(&mut self, tag: u8, contents: &[u8]) -> &mut Encoder {
107        self.bytes.push(tag);
108        encode_length(contents.len(), &mut self.bytes);
109        self.bytes.extend_from_slice(contents);
110        self
111    }
112
113    /// A constructed element whose contents are written by `body`.
114    pub fn constructed(&mut self, tag: u8, body: impl FnOnce(&mut Encoder)) -> &mut Encoder {
115        debug_assert!(tag & CONSTRUCTED != 0, "a constructed element needs its constructed bit");
116        let mut inner = Encoder::new();
117        body(&mut inner);
118        self.element(tag, &inner.bytes)
119    }
120
121    pub fn sequence(&mut self, body: impl FnOnce(&mut Encoder)) -> &mut Encoder {
122        self.constructed(SEQUENCE, body)
123    }
124
125    pub fn set(&mut self, body: impl FnOnce(&mut Encoder)) -> &mut Encoder {
126        self.constructed(SET, body)
127    }
128
129    pub fn integer(&mut self, value: i64) -> &mut Encoder {
130        self.tagged_integer(INTEGER, value)
131    }
132
133    pub fn enumerated(&mut self, value: i64) -> &mut Encoder {
134        self.tagged_integer(ENUMERATED, value)
135    }
136
137    /// An INTEGER under some other tag — LDAP tags a few of them contextually.
138    pub fn tagged_integer(&mut self, tag: u8, value: i64) -> &mut Encoder {
139        self.element(tag, &integer_contents(value))
140    }
141
142    /// TRUE is encoded as `0xff`.
143    ///
144    /// BER would accept any non-zero octet, but there is no reason to invent a
145    /// second spelling on the way out, and DER-shaped output is what every
146    /// directory expects to see.
147    pub fn boolean(&mut self, value: bool) -> &mut Encoder {
148        self.element(BOOLEAN, &[if value { 0xff } else { 0x00 }])
149    }
150
151    pub fn octet_string(&mut self, contents: &[u8]) -> &mut Encoder {
152        self.element(OCTET_STRING, contents)
153    }
154
155    /// An `LDAPString`, which RFC 4511 defines as UTF-8 in an OCTET STRING.
156    pub fn string(&mut self, value: &str) -> &mut Encoder {
157        self.element(OCTET_STRING, value.as_bytes())
158    }
159
160    /// An OCTET STRING carrying a context-specific tag instead of its own —
161    /// implicit tagging, which is what RFC 4511's ASN.1 module asks for.
162    pub fn tagged_string(&mut self, tag: u8, value: &str) -> &mut Encoder {
163        self.element(tag, value.as_bytes())
164    }
165
166    pub fn null(&mut self) -> &mut Encoder {
167        self.element(NULL, &[])
168    }
169
170    /// Append bytes that are already a complete element.
171    pub fn raw(&mut self, encoded: &[u8]) -> &mut Encoder {
172        self.bytes.extend_from_slice(encoded);
173        self
174    }
175}
176
177/// The definite length, in the fewest octets that can hold it.
178fn encode_length(length: usize, out: &mut Vec<u8>) {
179    if length < 0x80 {
180        out.push(length as u8);
181        return;
182    }
183
184    let bytes = length.to_be_bytes();
185    let first = bytes.iter().position(|&byte| byte != 0).unwrap_or(bytes.len() - 1);
186    let significant = &bytes[first..];
187
188    out.push(0x80 | significant.len() as u8);
189    out.extend_from_slice(significant);
190}
191
192/// Two's complement, in the fewest octets — X.690 §8.3.2.
193fn integer_contents(value: i64) -> Vec<u8> {
194    let mut bytes = value.to_be_bytes().to_vec();
195
196    while bytes.len() > 1 {
197        // A leading 0x00 in front of a positive byte, or a leading 0xff in
198        // front of a negative one, carries no information.
199        let redundant = (bytes[0] == 0x00 && bytes[1] & 0x80 == 0)
200            || (bytes[0] == 0xff && bytes[1] & 0x80 != 0);
201        if !redundant {
202            break;
203        }
204        bytes.remove(0);
205    }
206
207    bytes
208}
209
210// ---------------------------------------------------------------------------
211// Decoding
212// ---------------------------------------------------------------------------
213
214/// Read an identifier octet and a definite length.
215///
216/// Returns `Ok(None)` when the header itself is not all here yet — the framing
217/// case, where the caller should read more from the socket. A header that is
218/// present but *wrong* is an error, not a request for more bytes: waiting for
219/// more of a message that has already told you it is malformed is how a client
220/// hangs on a hostile server.
221fn read_header(bytes: &[u8]) -> Result<Option<(u8, usize, usize)>> {
222    let Some(&tag) = bytes.first() else {
223        return Ok(None);
224    };
225
226    if tag & 0x1f == 0x1f {
227        return Err(Error::msg(
228            "BER high-tag-number form is refused: LDAP's highest tag number is 25, so a \
229             multi-byte tag is not something a directory has any way to mean",
230        ));
231    }
232
233    let Some(&first) = bytes.get(1) else {
234        return Ok(None);
235    };
236
237    if first == 0x80 {
238        return Err(Error::msg(
239            "BER indefinite length is refused: RFC 4511 §5.1 uses the definite form only, and a \
240             message whose end has to be searched for is a message whose end can be moved",
241        ));
242    }
243
244    if first < 0x80 {
245        return Ok(Some((tag, 2, first as usize)));
246    }
247
248    let count = (first & 0x7f) as usize;
249    if count > 4 {
250        return Err(Error::msg(format!(
251            "BER length is spread over {count} octets. Nothing in an LDAP message is four \
252             gigabytes long, so this is a header worth refusing rather than believing"
253        )));
254    }
255
256    if bytes.len() < 2 + count {
257        return Ok(None);
258    }
259
260    let mut length = 0usize;
261    for &byte in &bytes[2..2 + count] {
262        length = (length << 8) | byte as usize;
263    }
264
265    if length < 0x80 || bytes[2] == 0 {
266        return Err(Error::msg(format!(
267            "BER length {length} is not in its shortest form. X.690 requires one encoding per \
268             number; a padded one is a second spelling, and two spellings is how two parsers \
269             come to disagree about the same bytes"
270        )));
271    }
272
273    Ok(Some((tag, 2 + count, length)))
274}
275
276/// The total size of the element starting at `bytes[0]`, header included.
277///
278/// `Ok(None)` means "not yet" — the header is incomplete, or the contents have
279/// not all arrived. This is the framing primitive the connection reads with, so
280/// it is also where the size limit is enforced: the length is checked against
281/// `limit` before the caller is ever told to keep reading, which is what stops
282/// a server from asking a client to buffer a gigabyte.
283pub fn element_size(bytes: &[u8], limit: usize) -> Result<Option<usize>> {
284    let Some((_, header, length)) = read_header(bytes)? else {
285        return Ok(None);
286    };
287
288    let total = header
289        .checked_add(length)
290        .ok_or_else(|| Error::msg("BER element length overflows a usize"))?;
291
292    if total > limit {
293        return Err(Error::msg(format!(
294            "the directory announced a {total} byte message; the limit is {limit}. Raise it \
295             deliberately with `LdapConfig::max_message_size` if a legitimate search really \
296             does return that much"
297        )));
298    }
299
300    if bytes.len() < total {
301        return Ok(None);
302    }
303
304    Ok(Some(total))
305}
306
307/// A cursor over a sequence of BER elements.
308///
309/// LDAP's shapes are all known ahead of time, so this reads them positionally
310/// rather than building a value tree: `decoder.integer()?` then
311/// `decoder.nested(tag)?`, mirroring the ASN.1 in RFC 4511 line for line.
312pub struct Decoder<'a> {
313    bytes: &'a [u8],
314    position: usize,
315    depth: usize,
316}
317
318impl<'a> Decoder<'a> {
319    pub fn new(bytes: &'a [u8]) -> Decoder<'a> {
320        Decoder { bytes, position: 0, depth: 0 }
321    }
322
323    pub fn is_empty(&self) -> bool {
324        self.position >= self.bytes.len()
325    }
326
327    pub fn remaining(&self) -> &'a [u8] {
328        &self.bytes[self.position..]
329    }
330
331    /// The identifier octet of the next element, without consuming it.
332    ///
333    /// Optional fields are how LDAP says "and maybe a referral", so every
334    /// optional read starts here.
335    pub fn peek_tag(&self) -> Option<u8> {
336        self.bytes.get(self.position).copied()
337    }
338
339    pub fn has_tag(&self, tag: u8) -> bool {
340        self.peek_tag() == Some(tag)
341    }
342
343    /// The next element: its tag and its contents.
344    pub fn element(&mut self) -> Result<(u8, &'a [u8])> {
345        let rest = &self.bytes[self.position..];
346
347        let (tag, header, length) = read_header(rest)?
348            .ok_or_else(|| Error::msg("BER ended in the middle of an element header"))?;
349
350        let end = header
351            .checked_add(length)
352            .ok_or_else(|| Error::msg("BER element length overflows a usize"))?;
353
354        if end > rest.len() {
355            return Err(Error::msg(format!(
356                "a BER element claims {length} content bytes but only {} remain in the message",
357                rest.len() - header
358            )));
359        }
360
361        self.position += end;
362        Ok((tag, &rest[header..end]))
363    }
364
365    /// The contents of the next element, which must carry exactly `tag`.
366    pub fn expect(&mut self, tag: u8) -> Result<&'a [u8]> {
367        let found = self.peek_tag();
368        let (actual, contents) = self.element()?;
369        if actual != tag {
370            return Err(Error::msg(format!(
371                "expected a BER element tagged {tag:#04x} but found {:#04x}",
372                found.unwrap_or(actual)
373            )));
374        }
375        Ok(contents)
376    }
377
378    /// Step into a constructed element, one level deeper.
379    pub fn nested(&mut self, tag: u8) -> Result<Decoder<'a>> {
380        if self.depth + 1 > MAX_DEPTH {
381            return Err(Error::msg(format!(
382                "BER nested deeper than {MAX_DEPTH} levels; a filter that deep is not one a \
383                 directory built"
384            )));
385        }
386        let contents = self.expect(tag)?;
387        Ok(Decoder { bytes: contents, position: 0, depth: self.depth + 1 })
388    }
389
390    pub fn sequence(&mut self) -> Result<Decoder<'a>> {
391        self.nested(SEQUENCE)
392    }
393
394    pub fn set(&mut self) -> Result<Decoder<'a>> {
395        self.nested(SET)
396    }
397
398    pub fn integer(&mut self) -> Result<i64> {
399        integer_from(self.expect(INTEGER)?)
400    }
401
402    pub fn enumerated(&mut self) -> Result<i64> {
403        integer_from(self.expect(ENUMERATED)?)
404    }
405
406    pub fn boolean(&mut self) -> Result<bool> {
407        match self.expect(BOOLEAN)? {
408            [byte] => Ok(*byte != 0),
409            other => Err(Error::msg(format!(
410                "a BER BOOLEAN has {} content octets; it must have exactly one",
411                other.len()
412            ))),
413        }
414    }
415
416    pub fn octet_string(&mut self) -> Result<&'a [u8]> {
417        self.expect(OCTET_STRING)
418    }
419
420    /// An `LDAPString` — an OCTET STRING that RFC 4511 promises is UTF-8.
421    ///
422    /// Attribute *values* are not read through here: they are arbitrary octets
423    /// (a `jpegPhoto` is not text), and forcing them through UTF-8 would turn a
424    /// perfectly good photograph into an error. Names, DNs and diagnostics are.
425    pub fn string(&mut self) -> Result<String> {
426        let bytes = self.octet_string()?;
427        utf8(bytes)
428    }
429
430    /// An implicitly tagged `LDAPString`.
431    pub fn tagged_string(&mut self, tag: u8) -> Result<String> {
432        let bytes = self.expect(tag)?;
433        utf8(bytes)
434    }
435
436    /// Consume the next element and throw it away.
437    ///
438    /// Used for fields a newer directory may add that this client does not
439    /// read. Skipping a known-shaped element is safe; skipping to a byte offset
440    /// is not, which is why this goes through the same header parser.
441    pub fn skip(&mut self) -> Result<()> {
442        self.element().map(|_| ())
443    }
444
445    /// Refuse anything left over.
446    ///
447    /// Trailing bytes mean the message was not what it claimed to be, and
448    /// ignoring them is how one message smuggles a second past a parser.
449    pub fn finish(&self, what: &str) -> Result<()> {
450        if !self.is_empty() {
451            return Err(Error::msg(format!(
452                "{what} had {} trailing bytes after a complete value",
453                self.bytes.len() - self.position
454            )));
455        }
456        Ok(())
457    }
458}
459
460/// A signed integer from its content octets.
461pub fn integer_from(contents: &[u8]) -> Result<i64> {
462    let Some((&first, rest)) = contents.split_first() else {
463        return Err(Error::msg("a BER INTEGER has no content octets; it must have at least one"));
464    };
465
466    if contents.len() > 8 {
467        return Err(Error::msg(format!(
468            "a BER INTEGER of {} octets does not fit in an i64; nothing LDAP defines is that \
469             wide",
470            contents.len()
471        )));
472    }
473
474    // X.690 §8.3.2: the first nine bits may not be all zero or all one. That is
475    // the rule that gives every integer exactly one encoding, and it is a BER
476    // rule, not just a DER one — so a padded integer is malformed, not lenient.
477    if let Some(&second) = rest.first()
478        && ((first == 0x00 && second & 0x80 == 0) || (first == 0xff && second & 0x80 != 0))
479    {
480        return Err(Error::msg(
481            "a BER INTEGER has a redundant leading octet; X.690 requires the shortest form",
482        ));
483    }
484
485    let mut value = first as i8 as i64;
486    for &byte in rest {
487        value = (value << 8) | byte as i64;
488    }
489    Ok(value)
490}
491
492fn utf8(bytes: &[u8]) -> Result<String> {
493    String::from_utf8(bytes.to_vec())
494        .map_err(|_| Error::msg("an LDAPString from the directory is not valid UTF-8"))
495}
496
497#[cfg(test)]
498mod tests {
499    use super::*;
500
501    fn encoded(body: impl FnOnce(&mut Encoder)) -> Vec<u8> {
502        let mut encoder = Encoder::new();
503        body(&mut encoder);
504        encoder.into_bytes()
505    }
506
507    /// The error's text, for results whose `Ok` type has no `Debug` — which is
508    /// most of them here, on purpose.
509    fn failure<T>(result: Result<T>) -> String {
510        match result {
511            Ok(_) => panic!("expected this to fail"),
512            Err(error) => error.to_string(),
513        }
514    }
515
516    #[test]
517    fn integers_use_the_shortest_two_s_complement_form() {
518        // The boundaries are the whole story: 127 fits in one octet, 128 needs
519        // a zero in front of it so it does not read as -128.
520        assert_eq!(encoded(|e| { e.integer(0); }), vec![0x02, 0x01, 0x00]);
521        assert_eq!(encoded(|e| { e.integer(127); }), vec![0x02, 0x01, 0x7f]);
522        assert_eq!(encoded(|e| { e.integer(128); }), vec![0x02, 0x02, 0x00, 0x80]);
523        assert_eq!(encoded(|e| { e.integer(256); }), vec![0x02, 0x02, 0x01, 0x00]);
524        assert_eq!(encoded(|e| { e.integer(-1); }), vec![0x02, 0x01, 0xff]);
525        assert_eq!(encoded(|e| { e.integer(-128); }), vec![0x02, 0x01, 0x80]);
526        assert_eq!(encoded(|e| { e.integer(-129); }), vec![0x02, 0x02, 0xff, 0x7f]);
527        assert_eq!(
528            encoded(|e| { e.integer(i64::MAX); }),
529            vec![0x02, 0x08, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
530        );
531    }
532
533    #[test]
534    fn integers_round_trip_through_their_encoding() {
535        for value in [0, 1, -1, 127, 128, -128, -129, 255, 256, 65_535, 65_536, -65_536] {
536            let bytes = encoded(|e| {
537                e.integer(value);
538            });
539            assert_eq!(Decoder::new(&bytes).integer().unwrap(), value, "{value}");
540        }
541        for value in [i64::MIN, i64::MAX, i32::MIN as i64, i32::MAX as i64] {
542            let bytes = encoded(|e| {
543                e.integer(value);
544            });
545            assert_eq!(Decoder::new(&bytes).integer().unwrap(), value, "{value}");
546        }
547    }
548
549    #[test]
550    fn a_padded_integer_is_refused_rather_than_read_leniently() {
551        // 0x02 0x02 0x00 0x7f is 127 written the long way. Reading it happily
552        // means this parser and the directory's disagree about which byte
553        // strings are legal, which is exactly the gap worth closing.
554        let error = Decoder::new(&[0x02, 0x02, 0x00, 0x7f]).integer().unwrap_err().to_string();
555        assert!(error.contains("redundant leading octet"), "got {error}");
556
557        let error = Decoder::new(&[0x02, 0x02, 0xff, 0x80]).integer().unwrap_err().to_string();
558        assert!(error.contains("redundant leading octet"), "got {error}");
559
560        // Empty contents are not zero; they are nothing.
561        let error = Decoder::new(&[0x02, 0x00]).integer().unwrap_err().to_string();
562        assert!(error.contains("no content octets"), "got {error}");
563    }
564
565    #[test]
566    fn booleans_strings_and_sequences_encode_the_way_x690_says() {
567        assert_eq!(encoded(|e| { e.boolean(true); }), vec![0x01, 0x01, 0xff]);
568        assert_eq!(encoded(|e| { e.boolean(false); }), vec![0x01, 0x01, 0x00]);
569        assert_eq!(encoded(|e| { e.string("abc"); }), vec![0x04, 0x03, b'a', b'b', b'c']);
570        assert_eq!(encoded(|e| { e.string(""); }), vec![0x04, 0x00]);
571        assert_eq!(encoded(|e| { e.sequence(|_| {}); }), vec![0x30, 0x00]);
572        assert_eq!(encoded(|e| { e.enumerated(49); }), vec![0x0a, 0x01, 0x31]);
573
574        let nested = encoded(|e| {
575            e.sequence(|body| {
576                body.integer(1);
577                body.string("x");
578            });
579        });
580        assert_eq!(nested, vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x04, 0x01, b'x']);
581    }
582
583    #[test]
584    fn long_lengths_use_as_few_octets_as_the_number_needs() {
585        // 127 content bytes fits the short form; 128 does not, and the boundary
586        // is the only interesting place in the whole length encoding.
587        let short = encoded(|e| {
588            e.octet_string(&[0u8; 127]);
589        });
590        assert_eq!(&short[..2], &[0x04, 0x7f]);
591
592        let long = encoded(|e| {
593            e.octet_string(&[0u8; 128]);
594        });
595        assert_eq!(&long[..3], &[0x04, 0x81, 0x80]);
596
597        let longer = encoded(|e| {
598            e.octet_string(&[0u8; 300]);
599        });
600        assert_eq!(&longer[..4], &[0x04, 0x82, 0x01, 0x2c]);
601
602        assert_eq!(Decoder::new(&longer).octet_string().unwrap().len(), 300);
603    }
604
605    #[test]
606    fn a_padded_length_is_refused() {
607        // 0x04 0x82 0x00 0x03 is "three" written in two octets.
608        let error =
609            Decoder::new(&[0x04, 0x82, 0x00, 0x03, 1, 2, 3]).octet_string().unwrap_err().to_string();
610        assert!(error.contains("shortest form"), "got {error}");
611
612        // And so is the long form used for a number the short form could hold.
613        let error =
614            Decoder::new(&[0x04, 0x81, 0x03, 1, 2, 3]).octet_string().unwrap_err().to_string();
615        assert!(error.contains("shortest form"), "got {error}");
616    }
617
618    #[test]
619    fn indefinite_lengths_are_refused_rather_than_parsed() {
620        // RFC 4511 §5.1 forbids them, so a directory cannot legitimately send
621        // one — which makes anything that does worth rejecting outright.
622        let error = failure(Decoder::new(&[0x30, 0x80, 0x02, 0x01, 0x01, 0x00, 0x00]).sequence());
623        assert!(error.contains("indefinite"), "got {error}");
624    }
625
626    #[test]
627    fn a_multi_byte_tag_is_refused() {
628        let error = Decoder::new(&[0x1f, 0x81, 0x00, 0x00]).element().unwrap_err().to_string();
629        assert!(error.contains("high-tag-number"), "got {error}");
630    }
631
632    #[test]
633    fn a_length_longer_than_the_message_is_refused_before_allocating() {
634        // The header claims sixteen megabytes; the message is seven bytes.
635        // Believing it would allocate first and find out afterwards.
636        let error = Decoder::new(&[0x04, 0x84, 0x01, 0x00, 0x00, 0x00, 0x00])
637            .octet_string()
638            .unwrap_err()
639            .to_string();
640        assert!(error.contains("only"), "got {error}");
641    }
642
643    #[test]
644    fn deep_nesting_is_bounded() {
645        // MAX_DEPTH + 2 nested SEQUENCEs — a few dozen bytes that would
646        // otherwise recurse until the stack gives out.
647        let mut bytes = vec![0x30, 0x00];
648        for _ in 0..MAX_DEPTH + 2 {
649            let mut outer = vec![0x30, bytes.len() as u8];
650            outer.extend_from_slice(&bytes);
651            bytes = outer;
652        }
653
654        fn descend(decoder: &mut Decoder<'_>) -> Result<()> {
655            let mut inner = decoder.sequence()?;
656            if !inner.is_empty() {
657                descend(&mut inner)?;
658            }
659            Ok(())
660        }
661
662        let error = descend(&mut Decoder::new(&bytes)).unwrap_err().to_string();
663        assert!(error.contains("nested deeper"), "got {error}");
664    }
665
666    #[test]
667    fn truncation_anywhere_is_an_error_and_never_a_panic() {
668        // Every prefix of a valid message must be refused cleanly — this is the
669        // shape of input a hostile server controls completely.
670        let complete = encoded(|e| {
671            e.sequence(|body| {
672                body.integer(7);
673                body.string("hello");
674                body.boolean(true);
675            });
676        });
677
678        for length in 0..complete.len() {
679            let mut decoder = Decoder::new(&complete[..length]);
680            let read = (|| -> Result<()> {
681                let mut inner = decoder.sequence()?;
682                inner.integer()?;
683                inner.string()?;
684                inner.boolean()?;
685                Ok(())
686            })();
687            assert!(read.is_err(), "a {length} byte prefix should not parse");
688        }
689    }
690
691    #[test]
692    fn framing_says_not_yet_rather_than_guessing() {
693        let message = encoded(|e| {
694            e.sequence(|body| {
695                body.integer(1);
696                body.string("abcdefghij");
697            });
698        });
699
700        // One byte at a time: nothing is claimed until the whole element is in.
701        for length in 0..message.len() {
702            assert_eq!(
703                element_size(&message[..length], 1 << 20).unwrap(),
704                None,
705                "{length} bytes should not frame a complete message"
706            );
707        }
708        assert_eq!(element_size(&message, 1 << 20).unwrap(), Some(message.len()));
709
710        // Trailing bytes belong to the next message, not this one.
711        let mut two = message.clone();
712        two.extend_from_slice(&message);
713        assert_eq!(element_size(&two, 1 << 20).unwrap(), Some(message.len()));
714    }
715
716    #[test]
717    fn framing_refuses_an_oversized_message_before_buffering_it() {
718        // A four-byte header announcing sixty-four megabytes. The point of the
719        // limit is that this costs nothing: no read loop, no allocation.
720        let header = [0x30, 0x84, 0x04, 0x00, 0x00, 0x00];
721        let error = element_size(&header, 1 << 20).unwrap_err().to_string();
722        assert!(error.contains("the limit is"), "got {error}");
723    }
724
725    #[test]
726    fn a_wrong_tag_names_both_tags() {
727        let bytes = encoded(|e| {
728            e.string("not an integer");
729        });
730        let error = Decoder::new(&bytes).integer().unwrap_err().to_string();
731        assert!(error.contains("0x02") && error.contains("0x04"), "got {error}");
732    }
733
734    #[test]
735    fn trailing_bytes_after_a_value_are_refused() {
736        let bytes = encoded(|e| {
737            e.sequence(|body| {
738                body.integer(1);
739                body.integer(2);
740            });
741        });
742        let mut sequence = Decoder::new(&bytes).sequence().unwrap();
743        sequence.integer().unwrap();
744        assert!(sequence.finish("the sequence").is_err());
745        sequence.integer().unwrap();
746        assert!(sequence.finish("the sequence").is_ok());
747    }
748
749    #[test]
750    fn context_and_application_tags_are_built_from_their_parts() {
751        // BindRequest is [APPLICATION 0] SEQUENCE, BindResponse [APPLICATION 1],
752        // and the simple authentication choice is [0] primitive.
753        assert_eq!(application(0, true), 0x60);
754        assert_eq!(application(1, true), 0x61);
755        assert_eq!(application(2, false), 0x42);
756        assert_eq!(application(23, true), 0x77);
757        assert_eq!(context(0, false), 0x80);
758        assert_eq!(context(0, true), 0xa0);
759        assert_eq!(context(7, false), 0x87);
760    }
761
762    #[test]
763    fn a_non_utf8_ldapstring_is_refused_but_a_raw_value_is_not() {
764        let bytes = encoded(|e| {
765            e.octet_string(&[0xff, 0xfe]);
766        });
767        assert!(Decoder::new(&bytes).string().is_err());
768        // The same octets read as an attribute value, which they are allowed
769        // to be: a photograph is not text.
770        assert_eq!(Decoder::new(&bytes).octet_string().unwrap(), &[0xff, 0xfe]);
771    }
772}