pub const MAX_BSON_DEPTH: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BsonScan {
Complete { consumed: usize },
TooDeep { limit: usize },
Malformed,
}
pub fn scan_bson_document(data: &[u8]) -> BsonScan {
scan_bson_document_with_limit(data, MAX_BSON_DEPTH)
}
pub fn scan_bson_document_with_limit(data: &[u8], max_depth: usize) -> BsonScan {
let max_depth = max_depth.min(MAX_BSON_DEPTH);
let mut ends = [0usize; MAX_BSON_DEPTH];
let mut depth = 0usize;
let Some(end) = document_end(data, 0, data.len()) else {
return BsonScan::Malformed;
};
if max_depth == 0 {
return BsonScan::TooDeep { limit: max_depth };
}
ends[0] = end;
depth += 1;
let mut pos = 4usize;
loop {
let end = ends[depth - 1];
if pos == end - 1 {
pos = end;
depth -= 1;
if depth == 0 {
return BsonScan::Complete { consumed: end };
}
continue;
}
let limit = end - 1;
if pos > limit {
return BsonScan::Malformed;
}
let kind = data[pos];
pos += 1;
let Some(nul) = data[pos..limit].iter().position(|&b| b == 0) else {
return BsonScan::Malformed;
};
pos += nul + 1;
let size = match kind {
0x08 => 1, 0x10 => 4, 0x01 | 0x09 | 0x11 | 0x12 => 8, 0x07 => 12, 0x13 => 16, 0x06 | 0x0A | 0x7F | 0xFF => 0, 0x02 | 0x0D | 0x0E => match string_size(data, pos, limit) {
Some(n) => n,
None => return BsonScan::Malformed,
},
0x0C => match string_size(data, pos, limit) {
Some(n) => n + 12,
None => return BsonScan::Malformed,
},
0x05 => match read_i32(data, pos, limit).and_then(|n| usize::try_from(n).ok()) {
Some(n) => match n.checked_add(5) {
Some(n) => n,
None => return BsonScan::Malformed,
},
None => return BsonScan::Malformed,
},
0x0B => {
let Some(p) = data[pos..limit].iter().position(|&b| b == 0) else {
return BsonScan::Malformed;
};
let Some(o) = data[pos + p + 1..limit].iter().position(|&b| b == 0) else {
return BsonScan::Malformed;
};
p + 1 + o + 1
}
0x03 | 0x04 => {
let Some(child_end) = document_end(data, pos, limit) else {
return BsonScan::Malformed;
};
if depth >= max_depth {
return BsonScan::TooDeep { limit: max_depth };
}
ends[depth] = child_end;
depth += 1;
pos += 4;
continue;
}
0x0F => {
let Some(total) = read_i32(data, pos, limit).and_then(|n| usize::try_from(n).ok())
else {
return BsonScan::Malformed;
};
if total < 14 {
return BsonScan::Malformed;
}
let element_end = match pos.checked_add(total) {
Some(e) if e <= limit => e,
_ => return BsonScan::Malformed,
};
let Some(code) = string_size(data, pos + 4, element_end) else {
return BsonScan::Malformed;
};
let scope = pos + 4 + code;
match document_end(data, scope, element_end) {
Some(scope_end) if scope_end == element_end => {}
_ => return BsonScan::Malformed,
}
if depth >= max_depth {
return BsonScan::TooDeep { limit: max_depth };
}
ends[depth] = element_end;
depth += 1;
pos = scope + 4;
continue;
}
_ => return BsonScan::Malformed,
};
match pos.checked_add(size) {
Some(next) if next <= limit => pos = next,
_ => return BsonScan::Malformed,
}
}
}
fn document_end(data: &[u8], start: usize, limit: usize) -> Option<usize> {
let len = usize::try_from(read_i32(data, start, limit)?).ok()?;
if len < 5 {
return None;
}
let end = start.checked_add(len)?;
if end > limit || data[end - 1] != 0 {
return None;
}
Some(end)
}
fn string_size(data: &[u8], start: usize, limit: usize) -> Option<usize> {
let len = usize::try_from(read_i32(data, start, limit)?).ok()?;
if len < 1 {
return None;
}
let size = len.checked_add(4)?;
if start.checked_add(size)? > limit {
return None;
}
Some(size)
}
fn read_i32(data: &[u8], start: usize, limit: usize) -> Option<i32> {
let bytes = data.get(start..start.checked_add(4)?)?;
if start + 4 > limit {
return None;
}
Some(i32::from_le_bytes(bytes.try_into().ok()?))
}