use core::fmt;
use zeroize::Zeroize;
use crate::{Error, SecureBytes};
pub const FORMAT_VERSION: u8 = 1;
pub(crate) const MAX_VARINT_LEN: usize = 10;
pub(crate) fn write_varint(buffer: &mut SecureBytes, value: usize) -> Result<(), Error> {
let mut scratch = [0u8; MAX_VARINT_LEN];
let mut remaining = value;
let mut len = 0;
loop {
let byte = (remaining & 0x7F) as u8;
remaining >>= 7;
if remaining == 0 {
scratch[len] = byte;
len += 1;
break;
}
scratch[len] = byte | 0x80;
len += 1;
}
let result = buffer.extend_from_slice(&scratch[..len]);
scratch.zeroize();
result
}
pub(crate) fn read_varint(buf: &[u8], pos: &mut usize) -> Result<usize, DecodeError> {
let mut result: u64 = 0;
let mut shift: u32 = 0;
let mut read: usize = 0;
loop {
let byte = match buf.get(*pos) {
Some(byte) => *byte,
None => return Err(DecodeError::UnexpectedEnd),
};
*pos += 1;
read += 1;
let chunk = u64::from(byte & 0x7F);
if shift >= u64::BITS || (chunk << shift) >> shift != chunk {
return Err(DecodeError::InvalidVarint);
}
result |= chunk << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
if read >= MAX_VARINT_LEN {
return Err(DecodeError::InvalidVarint);
}
}
usize::try_from(result).map_err(|_| DecodeError::InvalidVarint)
}
#[derive(Debug)]
#[non_exhaustive]
pub enum EncodeError {
Secure(Error),
LengthOverflow,
ElementCountMismatch,
Unsupported(&'static str),
Custom,
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Secure(error) => write!(f, "Failed to allocate the secure buffer: {error}"),
Self::LengthOverflow => write!(
f,
"Value is too large for the format's length field"
),
Self::ElementCountMismatch => write!(
f,
"A container declared a different number of elements than it wrote"
),
Self::Unsupported(what) => write!(f, "The binary codec does not support {what}"),
Self::Custom => write!(f, "Failed to encode the value"),
}
}
}
impl core::error::Error for EncodeError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Secure(error) => Some(error),
Self::LengthOverflow
| Self::ElementCountMismatch
| Self::Unsupported(_)
| Self::Custom => None,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum DecodeError {
UnexpectedEnd,
InvalidVarint,
InvalidLength,
InvalidUtf8,
InvalidBool,
InvalidOptionTag,
InvalidChar,
UnsupportedVersion(u8),
TrailingBytes {
extra: usize,
},
FrameMismatch {
unconsumed: usize,
},
Unsupported(&'static str),
Custom,
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedEnd => write!(f, "Input ended before the value was complete"),
Self::InvalidVarint => write!(f, "Invalid length prefix"),
Self::InvalidLength => write!(f, "Length prefix exceeds the remaining input"),
Self::InvalidUtf8 => write!(f, "Bytes are not valid UTF-8"),
Self::InvalidBool => write!(f, "Invalid bool tag"),
Self::InvalidOptionTag => write!(f, "Invalid Option tag"),
Self::InvalidChar => write!(f, "Value is not a Unicode scalar value"),
Self::UnsupportedVersion(version) => {
write!(f, "Unsupported format version: {version}")
}
Self::TrailingBytes { extra } => {
write!(f, "{extra} bytes remained after the value")
}
Self::FrameMismatch { unconsumed } => {
write!(
f,
"Value left {unconsumed} bytes unconsumed inside its frame"
)
}
Self::Unsupported(what) => write!(f, "The binary codec does not support {what}"),
Self::Custom => write!(f, "Failed to decode the value"),
}
}
}
impl core::error::Error for DecodeError {}
impl serde::ser::Error for EncodeError {
fn custom<T: fmt::Display>(_msg: T) -> Self {
Self::Custom
}
}
impl serde::de::Error for DecodeError {
fn custom<T: fmt::Display>(_msg: T) -> Self {
Self::Custom
}
}
#[cfg(test)]
mod tests {
use super::*;
fn varint_bytes(value: usize) -> SecureBytes {
let mut buffer = SecureBytes::new_with_capacity(MAX_VARINT_LEN).unwrap();
write_varint(&mut buffer, value).unwrap();
buffer
}
#[test]
fn test_varint_appends_without_clobbering() {
let mut buffer = SecureBytes::new_with_capacity(16).unwrap();
buffer.extend_from_slice(b"ab").unwrap();
write_varint(&mut buffer, 300).unwrap();
buffer.unlock_slice(|bytes| assert_eq!(bytes, [b'a', b'b', 0xAC, 0x02]));
}
#[test]
fn test_varint_round_trip() {
let values = [
0usize,
1,
127,
128,
300,
u16::MAX.into(),
u32::MAX as usize,
usize::MAX,
];
for value in values {
let buffer = varint_bytes(value);
buffer.unlock_slice(|bytes| {
let mut pos = 0;
assert_eq!(
read_varint(bytes, &mut pos).unwrap(),
value,
"value {value}"
);
assert_eq!(
pos,
bytes.len(),
"value {value} consumed the whole varint"
);
});
}
}
#[test]
fn test_read_varint_rejects_truncated() {
let mut pos = 0;
assert!(matches!(
read_varint(&[], &mut pos),
Err(DecodeError::UnexpectedEnd)
));
let mut pos = 0;
assert!(matches!(
read_varint(&[0x80], &mut pos),
Err(DecodeError::UnexpectedEnd)
));
}
#[test]
fn test_read_varint_rejects_overlong() {
let mut pos = 0;
assert!(matches!(
read_varint(&[0xFF; MAX_VARINT_LEN + 1], &mut pos),
Err(DecodeError::InvalidVarint)
));
let mut pos = 0;
assert!(matches!(
read_varint(&[0xFF; MAX_VARINT_LEN], &mut pos),
Err(DecodeError::InvalidVarint)
));
}
#[test]
fn test_read_varint_accepts_usize_max_encoding() {
let encoded = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01];
let mut pos = 0;
assert_eq!(
read_varint(&encoded, &mut pos).unwrap(),
usize::MAX
);
assert_eq!(pos, encoded.len());
}
#[test]
fn test_varint_zero_is_a_single_byte() {
varint_bytes(0).unlock_slice(|bytes| assert_eq!(bytes, [0x00]));
}
#[test]
fn test_varint_is_minimal() {
varint_bytes(127).unlock_slice(|bytes| assert_eq!(bytes.len(), 1));
varint_bytes(128).unlock_slice(|bytes| assert_eq!(bytes.len(), 2));
varint_bytes(16_383).unlock_slice(|bytes| assert_eq!(bytes.len(), 2));
varint_bytes(16_384).unlock_slice(|bytes| assert_eq!(bytes.len(), 3));
}
}