use core::fmt;
pub const QPACK_INTEGER_MAX: u64 = (1u64 << 62) - 1;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct QpackInteger<'a> {
bytes: &'a [u8],
prefix_bits: u8,
value: u64,
high_bits: u8,
}
impl<'a> QpackInteger<'a> {
pub fn parse(bytes: &'a [u8], prefix_bits: u8) -> Result<Self, QpackIntegerParseError> {
let prefix_mask = prefix_mask(prefix_bits)?;
let first = *bytes.first().ok_or(QpackIntegerParseError::Incomplete {
required: 1,
available: 0,
})?;
let prefix_value = first & prefix_mask;
let high_bits = first & !prefix_mask;
if prefix_value < prefix_mask {
return Ok(Self {
bytes: &bytes[..1],
prefix_bits,
value: u64::from(prefix_value),
high_bits,
});
}
let mut value = u64::from(prefix_mask);
let mut shift = 0u32;
let mut index = 1usize;
loop {
let octet = *bytes.get(index).ok_or(QpackIntegerParseError::Incomplete {
required: index
.checked_add(1)
.ok_or(QpackIntegerParseError::Overflow)?,
available: bytes.len(),
})?;
if shift >= u64::BITS {
return Err(QpackIntegerParseError::Overflow);
}
let contribution = u64::from(octet & 0x7f)
.checked_mul(1u64 << shift)
.ok_or(QpackIntegerParseError::Overflow)?;
value = value
.checked_add(contribution)
.ok_or(QpackIntegerParseError::Overflow)?;
index = index
.checked_add(1)
.ok_or(QpackIntegerParseError::Overflow)?;
if octet & 0x80 == 0 {
if value > QPACK_INTEGER_MAX {
return Err(QpackIntegerParseError::ValueTooLarge { value });
}
return Ok(Self {
bytes: &bytes[..index],
prefix_bits,
value,
high_bits,
});
}
shift = shift
.checked_add(7)
.ok_or(QpackIntegerParseError::Overflow)?;
}
}
pub const fn value(self) -> u64 {
self.value
}
pub const fn as_bytes(self) -> &'a [u8] {
self.bytes
}
pub const fn prefix_bits(self) -> u8 {
self.prefix_bits
}
pub const fn high_bits(self) -> u8 {
self.high_bits
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QpackIntegerParseError {
InvalidPrefixBits {
prefix_bits: u8,
},
Incomplete {
required: usize,
available: usize,
},
Overflow,
ValueTooLarge {
value: u64,
},
}
impl fmt::Display for QpackIntegerParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidPrefixBits { prefix_bits } => write!(
f,
"QPACK integer prefix width must be 1 through 8, got {prefix_bits}"
),
Self::Incomplete {
required,
available,
} => write!(
f,
"QPACK integer input is incomplete: need {required} bytes, have {available}"
),
Self::Overflow => f.write_str("QPACK integer exceeds u64"),
Self::ValueTooLarge { value } => {
write!(f, "QPACK integer exceeds the 62-bit limit: {value}")
}
}
}
}
pub struct QpackIntegerBuilder<'a> {
destination: &'a mut [u8],
prefix_bits: u8,
high_bits: u8,
value: u64,
}
impl<'a> QpackIntegerBuilder<'a> {
pub fn new(destination: &'a mut [u8], prefix_bits: u8, high_bits: u8, value: u64) -> Self {
Self {
destination,
prefix_bits,
high_bits,
value,
}
}
pub fn build(self) -> Result<QpackInteger<'a>, QpackIntegerBuildError> {
let prefix_mask = prefix_mask(self.prefix_bits).map_err(|_| {
QpackIntegerBuildError::InvalidPrefixBits {
prefix_bits: self.prefix_bits,
}
})?;
if self.high_bits & prefix_mask != 0 {
return Err(QpackIntegerBuildError::HighBitsOverlap {
high_bits: self.high_bits,
prefix_bits: self.prefix_bits,
});
}
if self.value > QPACK_INTEGER_MAX {
return Err(QpackIntegerBuildError::ValueTooLarge { value: self.value });
}
let required = encoded_len(self.value, prefix_mask);
if self.destination.len() < required {
return Err(QpackIntegerBuildError::BufferTooShort {
required,
available: self.destination.len(),
});
}
let bytes = &mut self.destination[..required];
write_canonical_prevalidated(bytes, self.prefix_bits, self.high_bits, self.value);
Ok(integer_from_prevalidated(
bytes,
self.prefix_bits,
self.high_bits,
self.value,
))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QpackIntegerBuildError {
InvalidPrefixBits {
prefix_bits: u8,
},
HighBitsOverlap {
high_bits: u8,
prefix_bits: u8,
},
ValueTooLarge {
value: u64,
},
BufferTooShort {
required: usize,
available: usize,
},
}
impl fmt::Display for QpackIntegerBuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidPrefixBits { prefix_bits } => write!(
f,
"QPACK integer prefix width must be 1 through 8, got {prefix_bits}"
),
Self::HighBitsOverlap {
high_bits,
prefix_bits,
} => write!(
f,
"QPACK integer high bits {high_bits:#04x} overlap the {prefix_bits}-bit prefix"
),
Self::ValueTooLarge { value } => {
write!(f, "QPACK integer exceeds the 62-bit limit: {value}")
}
Self::BufferTooShort {
required,
available,
} => write!(
f,
"QPACK integer buffer is too short: need {required} bytes, have {available}"
),
}
}
}
pub(super) fn canonical_encoded_len(value: u64, prefix_bits: u8) -> usize {
let prefix_mask = match prefix_bits {
1..=7 => (1u8 << prefix_bits) - 1,
8 => u8::MAX,
_ => 0,
};
encoded_len(value, prefix_mask)
}
pub(super) fn write_canonical_prevalidated(
bytes: &mut [u8],
prefix_bits: u8,
high_bits: u8,
value: u64,
) {
let prefix_mask = match prefix_bits {
1..=7 => (1u8 << prefix_bits) - 1,
8 => u8::MAX,
_ => 0,
};
if value < u64::from(prefix_mask) {
bytes[0] = high_bits | value as u8;
return;
}
bytes[0] = high_bits | prefix_mask;
let mut remaining = value - u64::from(prefix_mask);
let mut index = 1usize;
while remaining >= 0x80 {
bytes[index] = (remaining as u8 & 0x7f) | 0x80;
remaining >>= 7;
index += 1;
}
bytes[index] = remaining as u8;
}
pub(super) fn integer_from_prevalidated<'a>(
bytes: &'a [u8],
prefix_bits: u8,
high_bits: u8,
value: u64,
) -> QpackInteger<'a> {
QpackInteger {
bytes,
prefix_bits,
value,
high_bits,
}
}
fn prefix_mask(prefix_bits: u8) -> Result<u8, QpackIntegerParseError> {
match prefix_bits {
1..=7 => Ok((1u8 << prefix_bits) - 1),
8 => Ok(u8::MAX),
_ => Err(QpackIntegerParseError::InvalidPrefixBits { prefix_bits }),
}
}
fn encoded_len(value: u64, prefix_mask: u8) -> usize {
if value < u64::from(prefix_mask) {
return 1;
}
let mut required = 2usize;
let mut remaining = value - u64::from(prefix_mask);
while remaining >= 0x80 {
required += 1;
remaining >>= 7;
}
required
}