use std::result::Result;
const fn xdr_padding(bytes_len: usize) -> usize {
match bytes_len & 0b11 {
0 => 0,
1 => 3,
2 => 2,
3 => 1,
_ => unreachable!(),
}
}
pub(super) fn push_u32(buf: &mut Vec<u8>, value: u32) {
buf.extend_from_slice(&value.to_be_bytes());
}
pub(super) fn push_bytes(buf: &mut Vec<u8>, data: &[u8]) -> Result<(), String> {
let length = u32::try_from(data.len()).map_err(|error| format!("byte field length exceeds u32::MAX: {error}"))?;
push_u32(buf, length);
buf.extend_from_slice(data);
buf.extend(std::iter::repeat_n(0_u8, xdr_padding(data.len())));
Ok(())
}
pub(super) fn push_u16(buf: &mut Vec<u8>, value: u16) {
push_u32(buf, u32::from(value));
}
pub(super) fn push_bool(buf: &mut Vec<u8>, value: bool) {
push_u32(buf, u32::from(value));
}
pub(super) struct Reader<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
#[must_use]
pub(super) const fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
pub(super) fn take_u32(&mut self) -> Result<u32, &'static str> {
let end = self.pos.checked_add(4).ok_or("offset overflow")?;
let bytes = self.bytes.get(self.pos..end).ok_or("unexpected end of message")?;
let mut word = [0_u8; 4];
word.copy_from_slice(bytes);
self.pos = end;
Ok(u32::from_be_bytes(word))
}
pub(super) fn take_bytes(&mut self, max: usize) -> Result<Vec<u8>, String> {
let length =
usize::try_from(self.take_u32()?).map_err(|error| format!("byte field length overflows usize: {error}"))?;
if length > max {
return Err(format!("byte field of length {length} exceeds limit {max}"));
}
let end = self
.pos
.checked_add(length)
.ok_or_else(|| "byte field length overflows message".to_owned())?;
let data = self
.bytes
.get(self.pos..end)
.ok_or_else(|| "byte field extends past the message end".to_owned())?
.to_vec();
let padded_end = end
.checked_add(xdr_padding(length))
.ok_or_else(|| "padded length overflows".to_owned())?;
if padded_end > self.bytes.len() {
return Err("byte field padding extends past the message end".to_owned());
}
self.pos = padded_end;
Ok(data)
}
pub(super) fn take_bool(&mut self) -> Result<bool, &'static str> {
Ok(self.take_u32()? != 0)
}
pub(super) const fn finish(&self) -> Result<(), &'static str> {
if self.pos == self.bytes.len() {
Ok(())
} else {
Err("trailing bytes after the message fields")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn padding_follows_xdr() {
assert_eq!(xdr_padding(0), 0);
assert_eq!(xdr_padding(1), 3);
assert_eq!(xdr_padding(4), 0);
assert_eq!(xdr_padding(5), 3);
}
#[test]
fn push_and_take_bytes_round_trip() {
let mut buf = Vec::new();
push_bytes(&mut buf, b"ab").expect("small field");
let mut reader = Reader::new(&buf);
assert_eq!(reader.take_bytes(8).expect("read"), b"ab".to_vec());
assert_eq!(buf.len(), 8);
assert!(reader.finish().is_ok());
}
#[test]
fn oversize_fields_are_rejected() {
let mut buf = Vec::new();
push_bytes(&mut buf, b"abcdef").expect("small field");
let mut reader = Reader::new(&buf);
assert!(reader.take_bytes(4).is_err());
}
#[test]
fn truncated_fields_are_rejected() {
let mut buf = Vec::new();
push_u32(&mut buf, 16);
let mut reader = Reader::new(&buf);
assert!(reader.take_bytes(32).is_err());
}
#[test]
fn booleans_and_u16_are_four_byte_words() {
let mut buf = Vec::new();
push_u16(&mut buf, 0x0102);
push_bool(&mut buf, true);
assert_eq!(buf, vec![0, 0, 1, 2, 0, 0, 0, 1]);
let mut reader = Reader::new(&buf);
assert_eq!(reader.take_u32().expect("u16"), 0x0102);
assert!(reader.take_bool().expect("bool"));
assert!(reader.finish().is_ok());
}
}