use core::fmt;
use super::integer::{
HpackInteger, HpackIntegerParseError, canonical_encoded_len, canonical_integer,
write_canonical_for_valid_prefix,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HpackStringLiteral<'a> {
bytes: &'a [u8],
length: HpackInteger<'a>,
}
impl<'a> HpackStringLiteral<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, HpackStringLiteralParseError> {
let length =
HpackInteger::parse(bytes, 7).map_err(HpackStringLiteralParseError::InvalidLength)?;
let encoded_length = usize::try_from(length.value()).map_err(|_| {
HpackStringLiteralParseError::EncodedLengthNotRepresentable {
encoded_length: length.value(),
}
})?;
let length_length = length.as_bytes().len();
let total = length_length.checked_add(encoded_length).ok_or(
HpackStringLiteralParseError::TotalLengthOverflow {
length_length,
encoded_length,
},
)?;
if bytes.len() < total {
return Err(HpackStringLiteralParseError::IncompleteEncodedPayload {
required: encoded_length,
available: bytes.len() - length_length,
});
}
Ok(Self {
bytes: &bytes[..total],
length,
})
}
pub const fn as_bytes(self) -> &'a [u8] {
self.bytes
}
pub const fn is_huffman(self) -> bool {
self.length.high_bits() != 0
}
pub fn encoded_bytes(self) -> &'a [u8] {
&self.bytes[self.length.as_bytes().len()..]
}
pub const fn length(self) -> HpackInteger<'a> {
self.length
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HpackStringLiteralParseError {
InvalidLength(HpackIntegerParseError),
EncodedLengthNotRepresentable {
encoded_length: u64,
},
TotalLengthOverflow {
length_length: usize,
encoded_length: usize,
},
IncompleteEncodedPayload {
required: usize,
available: usize,
},
}
impl fmt::Display for HpackStringLiteralParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength(error) => write!(f, "invalid HPACK string length: {error}"),
Self::EncodedLengthNotRepresentable { encoded_length } => write!(
f,
"HPACK string encoded length {encoded_length} cannot be represented as usize"
),
Self::TotalLengthOverflow {
length_length,
encoded_length,
} => write!(
f,
"HPACK string total length overflows usize: length uses {length_length} bytes and payload is {encoded_length} bytes"
),
Self::IncompleteEncodedPayload {
required,
available,
} => write!(
f,
"HPACK string encoded payload is incomplete: need {required} bytes, have {available}"
),
}
}
}
pub struct HpackStringLiteralBuilder<'a, 'b> {
destination: &'a mut [u8],
huffman: bool,
encoded_bytes: &'b [u8],
}
impl<'a, 'b> HpackStringLiteralBuilder<'a, 'b> {
pub fn new_encoded(destination: &'a mut [u8], huffman: bool, encoded_bytes: &'b [u8]) -> Self {
Self {
destination,
huffman,
encoded_bytes,
}
}
pub fn build(self) -> Result<HpackStringLiteral<'a>, HpackStringLiteralBuildError> {
let encoded_length = u64::try_from(self.encoded_bytes.len()).map_err(|_| {
HpackStringLiteralBuildError::EncodedLengthNotRepresentable {
encoded_length: self.encoded_bytes.len(),
}
})?;
let length_length = canonical_encoded_len(encoded_length, 0x7f);
let total = length_length.checked_add(self.encoded_bytes.len()).ok_or(
HpackStringLiteralBuildError::TotalLengthOverflow {
length_length,
encoded_length: self.encoded_bytes.len(),
},
)?;
if self.destination.len() < total {
return Err(HpackStringLiteralBuildError::BufferTooShort {
required: total,
available: self.destination.len(),
});
}
let literal = &mut self.destination[..total];
let (length_destination, payload_destination) = literal.split_at_mut(length_length);
write_canonical_for_valid_prefix(
length_destination,
7,
0x7f,
if self.huffman { 0x80 } else { 0 },
encoded_length,
);
payload_destination.copy_from_slice(self.encoded_bytes);
let bytes = &*literal;
Ok(HpackStringLiteral {
bytes,
length: canonical_integer(
&bytes[..length_length],
7,
if self.huffman { 0x80 } else { 0 },
encoded_length,
),
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HpackStringLiteralBuildError {
EncodedLengthNotRepresentable {
encoded_length: usize,
},
TotalLengthOverflow {
length_length: usize,
encoded_length: usize,
},
BufferTooShort {
required: usize,
available: usize,
},
}
impl fmt::Display for HpackStringLiteralBuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EncodedLengthNotRepresentable { encoded_length } => write!(
f,
"HPACK string encoded length {encoded_length} cannot be represented as u64"
),
Self::TotalLengthOverflow {
length_length,
encoded_length,
} => write!(
f,
"HPACK string total length overflows usize: length uses {length_length} bytes and payload is {encoded_length} bytes"
),
Self::BufferTooShort {
required,
available,
} => write!(
f,
"HPACK string buffer is too short: need {required} bytes, have {available}"
),
}
}
}