use rustlavel_core::{Error, Result};
pub const UNIVERSAL: u8 = 0x00;
pub const APPLICATION: u8 = 0x40;
pub const CONTEXT: u8 = 0x80;
pub const CONSTRUCTED: u8 = 0x20;
pub const BOOLEAN: u8 = 0x01;
pub const INTEGER: u8 = 0x02;
pub const OCTET_STRING: u8 = 0x04;
pub const NULL: u8 = 0x05;
pub const ENUMERATED: u8 = 0x0a;
pub const SEQUENCE: u8 = 0x30;
pub const SET: u8 = 0x31;
pub const fn application(number: u8, constructed: bool) -> u8 {
APPLICATION | if constructed { CONSTRUCTED } else { 0 } | number
}
pub const fn context(number: u8, constructed: bool) -> u8 {
CONTEXT | if constructed { CONSTRUCTED } else { 0 } | number
}
pub const MAX_DEPTH: usize = 32;
#[derive(Default)]
pub struct Encoder {
bytes: Vec<u8>,
}
impl Encoder {
pub fn new() -> Encoder {
Encoder::default()
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
pub fn len(&self) -> usize {
self.bytes.len()
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
pub fn element(&mut self, tag: u8, contents: &[u8]) -> &mut Encoder {
self.bytes.push(tag);
encode_length(contents.len(), &mut self.bytes);
self.bytes.extend_from_slice(contents);
self
}
pub fn constructed(&mut self, tag: u8, body: impl FnOnce(&mut Encoder)) -> &mut Encoder {
debug_assert!(tag & CONSTRUCTED != 0, "a constructed element needs its constructed bit");
let mut inner = Encoder::new();
body(&mut inner);
self.element(tag, &inner.bytes)
}
pub fn sequence(&mut self, body: impl FnOnce(&mut Encoder)) -> &mut Encoder {
self.constructed(SEQUENCE, body)
}
pub fn set(&mut self, body: impl FnOnce(&mut Encoder)) -> &mut Encoder {
self.constructed(SET, body)
}
pub fn integer(&mut self, value: i64) -> &mut Encoder {
self.tagged_integer(INTEGER, value)
}
pub fn enumerated(&mut self, value: i64) -> &mut Encoder {
self.tagged_integer(ENUMERATED, value)
}
pub fn tagged_integer(&mut self, tag: u8, value: i64) -> &mut Encoder {
self.element(tag, &integer_contents(value))
}
pub fn boolean(&mut self, value: bool) -> &mut Encoder {
self.element(BOOLEAN, &[if value { 0xff } else { 0x00 }])
}
pub fn octet_string(&mut self, contents: &[u8]) -> &mut Encoder {
self.element(OCTET_STRING, contents)
}
pub fn string(&mut self, value: &str) -> &mut Encoder {
self.element(OCTET_STRING, value.as_bytes())
}
pub fn tagged_string(&mut self, tag: u8, value: &str) -> &mut Encoder {
self.element(tag, value.as_bytes())
}
pub fn null(&mut self) -> &mut Encoder {
self.element(NULL, &[])
}
pub fn raw(&mut self, encoded: &[u8]) -> &mut Encoder {
self.bytes.extend_from_slice(encoded);
self
}
}
fn encode_length(length: usize, out: &mut Vec<u8>) {
if length < 0x80 {
out.push(length as u8);
return;
}
let bytes = length.to_be_bytes();
let first = bytes.iter().position(|&byte| byte != 0).unwrap_or(bytes.len() - 1);
let significant = &bytes[first..];
out.push(0x80 | significant.len() as u8);
out.extend_from_slice(significant);
}
fn integer_contents(value: i64) -> Vec<u8> {
let mut bytes = value.to_be_bytes().to_vec();
while bytes.len() > 1 {
let redundant = (bytes[0] == 0x00 && bytes[1] & 0x80 == 0)
|| (bytes[0] == 0xff && bytes[1] & 0x80 != 0);
if !redundant {
break;
}
bytes.remove(0);
}
bytes
}
fn read_header(bytes: &[u8]) -> Result<Option<(u8, usize, usize)>> {
let Some(&tag) = bytes.first() else {
return Ok(None);
};
if tag & 0x1f == 0x1f {
return Err(Error::msg(
"BER high-tag-number form is refused: LDAP's highest tag number is 25, so a \
multi-byte tag is not something a directory has any way to mean",
));
}
let Some(&first) = bytes.get(1) else {
return Ok(None);
};
if first == 0x80 {
return Err(Error::msg(
"BER indefinite length is refused: RFC 4511 §5.1 uses the definite form only, and a \
message whose end has to be searched for is a message whose end can be moved",
));
}
if first < 0x80 {
return Ok(Some((tag, 2, first as usize)));
}
let count = (first & 0x7f) as usize;
if count > 4 {
return Err(Error::msg(format!(
"BER length is spread over {count} octets. Nothing in an LDAP message is four \
gigabytes long, so this is a header worth refusing rather than believing"
)));
}
if bytes.len() < 2 + count {
return Ok(None);
}
let mut length = 0usize;
for &byte in &bytes[2..2 + count] {
length = (length << 8) | byte as usize;
}
if length < 0x80 || bytes[2] == 0 {
return Err(Error::msg(format!(
"BER length {length} is not in its shortest form. X.690 requires one encoding per \
number; a padded one is a second spelling, and two spellings is how two parsers \
come to disagree about the same bytes"
)));
}
Ok(Some((tag, 2 + count, length)))
}
pub fn element_size(bytes: &[u8], limit: usize) -> Result<Option<usize>> {
let Some((_, header, length)) = read_header(bytes)? else {
return Ok(None);
};
let total = header
.checked_add(length)
.ok_or_else(|| Error::msg("BER element length overflows a usize"))?;
if total > limit {
return Err(Error::msg(format!(
"the directory announced a {total} byte message; the limit is {limit}. Raise it \
deliberately with `LdapConfig::max_message_size` if a legitimate search really \
does return that much"
)));
}
if bytes.len() < total {
return Ok(None);
}
Ok(Some(total))
}
pub struct Decoder<'a> {
bytes: &'a [u8],
position: usize,
depth: usize,
}
impl<'a> Decoder<'a> {
pub fn new(bytes: &'a [u8]) -> Decoder<'a> {
Decoder { bytes, position: 0, depth: 0 }
}
pub fn is_empty(&self) -> bool {
self.position >= self.bytes.len()
}
pub fn remaining(&self) -> &'a [u8] {
&self.bytes[self.position..]
}
pub fn peek_tag(&self) -> Option<u8> {
self.bytes.get(self.position).copied()
}
pub fn has_tag(&self, tag: u8) -> bool {
self.peek_tag() == Some(tag)
}
pub fn element(&mut self) -> Result<(u8, &'a [u8])> {
let rest = &self.bytes[self.position..];
let (tag, header, length) = read_header(rest)?
.ok_or_else(|| Error::msg("BER ended in the middle of an element header"))?;
let end = header
.checked_add(length)
.ok_or_else(|| Error::msg("BER element length overflows a usize"))?;
if end > rest.len() {
return Err(Error::msg(format!(
"a BER element claims {length} content bytes but only {} remain in the message",
rest.len() - header
)));
}
self.position += end;
Ok((tag, &rest[header..end]))
}
pub fn expect(&mut self, tag: u8) -> Result<&'a [u8]> {
let found = self.peek_tag();
let (actual, contents) = self.element()?;
if actual != tag {
return Err(Error::msg(format!(
"expected a BER element tagged {tag:#04x} but found {:#04x}",
found.unwrap_or(actual)
)));
}
Ok(contents)
}
pub fn nested(&mut self, tag: u8) -> Result<Decoder<'a>> {
if self.depth + 1 > MAX_DEPTH {
return Err(Error::msg(format!(
"BER nested deeper than {MAX_DEPTH} levels; a filter that deep is not one a \
directory built"
)));
}
let contents = self.expect(tag)?;
Ok(Decoder { bytes: contents, position: 0, depth: self.depth + 1 })
}
pub fn sequence(&mut self) -> Result<Decoder<'a>> {
self.nested(SEQUENCE)
}
pub fn set(&mut self) -> Result<Decoder<'a>> {
self.nested(SET)
}
pub fn integer(&mut self) -> Result<i64> {
integer_from(self.expect(INTEGER)?)
}
pub fn enumerated(&mut self) -> Result<i64> {
integer_from(self.expect(ENUMERATED)?)
}
pub fn boolean(&mut self) -> Result<bool> {
match self.expect(BOOLEAN)? {
[byte] => Ok(*byte != 0),
other => Err(Error::msg(format!(
"a BER BOOLEAN has {} content octets; it must have exactly one",
other.len()
))),
}
}
pub fn octet_string(&mut self) -> Result<&'a [u8]> {
self.expect(OCTET_STRING)
}
pub fn string(&mut self) -> Result<String> {
let bytes = self.octet_string()?;
utf8(bytes)
}
pub fn tagged_string(&mut self, tag: u8) -> Result<String> {
let bytes = self.expect(tag)?;
utf8(bytes)
}
pub fn skip(&mut self) -> Result<()> {
self.element().map(|_| ())
}
pub fn finish(&self, what: &str) -> Result<()> {
if !self.is_empty() {
return Err(Error::msg(format!(
"{what} had {} trailing bytes after a complete value",
self.bytes.len() - self.position
)));
}
Ok(())
}
}
pub fn integer_from(contents: &[u8]) -> Result<i64> {
let Some((&first, rest)) = contents.split_first() else {
return Err(Error::msg("a BER INTEGER has no content octets; it must have at least one"));
};
if contents.len() > 8 {
return Err(Error::msg(format!(
"a BER INTEGER of {} octets does not fit in an i64; nothing LDAP defines is that \
wide",
contents.len()
)));
}
if let Some(&second) = rest.first()
&& ((first == 0x00 && second & 0x80 == 0) || (first == 0xff && second & 0x80 != 0))
{
return Err(Error::msg(
"a BER INTEGER has a redundant leading octet; X.690 requires the shortest form",
));
}
let mut value = first as i8 as i64;
for &byte in rest {
value = (value << 8) | byte as i64;
}
Ok(value)
}
fn utf8(bytes: &[u8]) -> Result<String> {
String::from_utf8(bytes.to_vec())
.map_err(|_| Error::msg("an LDAPString from the directory is not valid UTF-8"))
}
#[cfg(test)]
mod tests {
use super::*;
fn encoded(body: impl FnOnce(&mut Encoder)) -> Vec<u8> {
let mut encoder = Encoder::new();
body(&mut encoder);
encoder.into_bytes()
}
fn failure<T>(result: Result<T>) -> String {
match result {
Ok(_) => panic!("expected this to fail"),
Err(error) => error.to_string(),
}
}
#[test]
fn integers_use_the_shortest_two_s_complement_form() {
assert_eq!(encoded(|e| { e.integer(0); }), vec![0x02, 0x01, 0x00]);
assert_eq!(encoded(|e| { e.integer(127); }), vec![0x02, 0x01, 0x7f]);
assert_eq!(encoded(|e| { e.integer(128); }), vec![0x02, 0x02, 0x00, 0x80]);
assert_eq!(encoded(|e| { e.integer(256); }), vec![0x02, 0x02, 0x01, 0x00]);
assert_eq!(encoded(|e| { e.integer(-1); }), vec![0x02, 0x01, 0xff]);
assert_eq!(encoded(|e| { e.integer(-128); }), vec![0x02, 0x01, 0x80]);
assert_eq!(encoded(|e| { e.integer(-129); }), vec![0x02, 0x02, 0xff, 0x7f]);
assert_eq!(
encoded(|e| { e.integer(i64::MAX); }),
vec![0x02, 0x08, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
);
}
#[test]
fn integers_round_trip_through_their_encoding() {
for value in [0, 1, -1, 127, 128, -128, -129, 255, 256, 65_535, 65_536, -65_536] {
let bytes = encoded(|e| {
e.integer(value);
});
assert_eq!(Decoder::new(&bytes).integer().unwrap(), value, "{value}");
}
for value in [i64::MIN, i64::MAX, i32::MIN as i64, i32::MAX as i64] {
let bytes = encoded(|e| {
e.integer(value);
});
assert_eq!(Decoder::new(&bytes).integer().unwrap(), value, "{value}");
}
}
#[test]
fn a_padded_integer_is_refused_rather_than_read_leniently() {
let error = Decoder::new(&[0x02, 0x02, 0x00, 0x7f]).integer().unwrap_err().to_string();
assert!(error.contains("redundant leading octet"), "got {error}");
let error = Decoder::new(&[0x02, 0x02, 0xff, 0x80]).integer().unwrap_err().to_string();
assert!(error.contains("redundant leading octet"), "got {error}");
let error = Decoder::new(&[0x02, 0x00]).integer().unwrap_err().to_string();
assert!(error.contains("no content octets"), "got {error}");
}
#[test]
fn booleans_strings_and_sequences_encode_the_way_x690_says() {
assert_eq!(encoded(|e| { e.boolean(true); }), vec![0x01, 0x01, 0xff]);
assert_eq!(encoded(|e| { e.boolean(false); }), vec![0x01, 0x01, 0x00]);
assert_eq!(encoded(|e| { e.string("abc"); }), vec![0x04, 0x03, b'a', b'b', b'c']);
assert_eq!(encoded(|e| { e.string(""); }), vec![0x04, 0x00]);
assert_eq!(encoded(|e| { e.sequence(|_| {}); }), vec![0x30, 0x00]);
assert_eq!(encoded(|e| { e.enumerated(49); }), vec![0x0a, 0x01, 0x31]);
let nested = encoded(|e| {
e.sequence(|body| {
body.integer(1);
body.string("x");
});
});
assert_eq!(nested, vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x04, 0x01, b'x']);
}
#[test]
fn long_lengths_use_as_few_octets_as_the_number_needs() {
let short = encoded(|e| {
e.octet_string(&[0u8; 127]);
});
assert_eq!(&short[..2], &[0x04, 0x7f]);
let long = encoded(|e| {
e.octet_string(&[0u8; 128]);
});
assert_eq!(&long[..3], &[0x04, 0x81, 0x80]);
let longer = encoded(|e| {
e.octet_string(&[0u8; 300]);
});
assert_eq!(&longer[..4], &[0x04, 0x82, 0x01, 0x2c]);
assert_eq!(Decoder::new(&longer).octet_string().unwrap().len(), 300);
}
#[test]
fn a_padded_length_is_refused() {
let error =
Decoder::new(&[0x04, 0x82, 0x00, 0x03, 1, 2, 3]).octet_string().unwrap_err().to_string();
assert!(error.contains("shortest form"), "got {error}");
let error =
Decoder::new(&[0x04, 0x81, 0x03, 1, 2, 3]).octet_string().unwrap_err().to_string();
assert!(error.contains("shortest form"), "got {error}");
}
#[test]
fn indefinite_lengths_are_refused_rather_than_parsed() {
let error = failure(Decoder::new(&[0x30, 0x80, 0x02, 0x01, 0x01, 0x00, 0x00]).sequence());
assert!(error.contains("indefinite"), "got {error}");
}
#[test]
fn a_multi_byte_tag_is_refused() {
let error = Decoder::new(&[0x1f, 0x81, 0x00, 0x00]).element().unwrap_err().to_string();
assert!(error.contains("high-tag-number"), "got {error}");
}
#[test]
fn a_length_longer_than_the_message_is_refused_before_allocating() {
let error = Decoder::new(&[0x04, 0x84, 0x01, 0x00, 0x00, 0x00, 0x00])
.octet_string()
.unwrap_err()
.to_string();
assert!(error.contains("only"), "got {error}");
}
#[test]
fn deep_nesting_is_bounded() {
let mut bytes = vec![0x30, 0x00];
for _ in 0..MAX_DEPTH + 2 {
let mut outer = vec![0x30, bytes.len() as u8];
outer.extend_from_slice(&bytes);
bytes = outer;
}
fn descend(decoder: &mut Decoder<'_>) -> Result<()> {
let mut inner = decoder.sequence()?;
if !inner.is_empty() {
descend(&mut inner)?;
}
Ok(())
}
let error = descend(&mut Decoder::new(&bytes)).unwrap_err().to_string();
assert!(error.contains("nested deeper"), "got {error}");
}
#[test]
fn truncation_anywhere_is_an_error_and_never_a_panic() {
let complete = encoded(|e| {
e.sequence(|body| {
body.integer(7);
body.string("hello");
body.boolean(true);
});
});
for length in 0..complete.len() {
let mut decoder = Decoder::new(&complete[..length]);
let read = (|| -> Result<()> {
let mut inner = decoder.sequence()?;
inner.integer()?;
inner.string()?;
inner.boolean()?;
Ok(())
})();
assert!(read.is_err(), "a {length} byte prefix should not parse");
}
}
#[test]
fn framing_says_not_yet_rather_than_guessing() {
let message = encoded(|e| {
e.sequence(|body| {
body.integer(1);
body.string("abcdefghij");
});
});
for length in 0..message.len() {
assert_eq!(
element_size(&message[..length], 1 << 20).unwrap(),
None,
"{length} bytes should not frame a complete message"
);
}
assert_eq!(element_size(&message, 1 << 20).unwrap(), Some(message.len()));
let mut two = message.clone();
two.extend_from_slice(&message);
assert_eq!(element_size(&two, 1 << 20).unwrap(), Some(message.len()));
}
#[test]
fn framing_refuses_an_oversized_message_before_buffering_it() {
let header = [0x30, 0x84, 0x04, 0x00, 0x00, 0x00];
let error = element_size(&header, 1 << 20).unwrap_err().to_string();
assert!(error.contains("the limit is"), "got {error}");
}
#[test]
fn a_wrong_tag_names_both_tags() {
let bytes = encoded(|e| {
e.string("not an integer");
});
let error = Decoder::new(&bytes).integer().unwrap_err().to_string();
assert!(error.contains("0x02") && error.contains("0x04"), "got {error}");
}
#[test]
fn trailing_bytes_after_a_value_are_refused() {
let bytes = encoded(|e| {
e.sequence(|body| {
body.integer(1);
body.integer(2);
});
});
let mut sequence = Decoder::new(&bytes).sequence().unwrap();
sequence.integer().unwrap();
assert!(sequence.finish("the sequence").is_err());
sequence.integer().unwrap();
assert!(sequence.finish("the sequence").is_ok());
}
#[test]
fn context_and_application_tags_are_built_from_their_parts() {
assert_eq!(application(0, true), 0x60);
assert_eq!(application(1, true), 0x61);
assert_eq!(application(2, false), 0x42);
assert_eq!(application(23, true), 0x77);
assert_eq!(context(0, false), 0x80);
assert_eq!(context(0, true), 0xa0);
assert_eq!(context(7, false), 0x87);
}
#[test]
fn a_non_utf8_ldapstring_is_refused_but_a_raw_value_is_not() {
let bytes = encoded(|e| {
e.octet_string(&[0xff, 0xfe]);
});
assert!(Decoder::new(&bytes).string().is_err());
assert_eq!(Decoder::new(&bytes).octet_string().unwrap(), &[0xff, 0xfe]);
}
}