use aws_smithy_types::config_bag::{Storable, StoreReplace};
use super::{
CHUNK_SIGNATURE_BEGIN, CHUNK_TERMINATOR, CRLF, DEFAULT_CHUNK_SIZE_BYTE, SIGNATURE_LENGTH,
};
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct AwsChunkedBodyOptions {
pub(crate) stream_length: u64,
pub(crate) trailer_lengths: Vec<u64>,
pub(crate) disabled: bool,
pub(crate) is_signed: bool,
pub(crate) chunk_size: Option<usize>,
}
impl Storable for AwsChunkedBodyOptions {
type Storer = StoreReplace<Self>;
}
impl AwsChunkedBodyOptions {
pub fn new(stream_length: u64, trailer_lengths: Vec<u64>) -> Self {
Self {
stream_length,
trailer_lengths,
disabled: false,
is_signed: false,
chunk_size: None,
}
}
pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
self.chunk_size = Some(chunk_size);
self
}
pub fn chunk_size(&self) -> usize {
self.chunk_size.unwrap_or(DEFAULT_CHUNK_SIZE_BYTE)
}
pub(super) fn total_trailer_length(&self) -> u64 {
self.trailer_lengths.iter().sum::<u64>()
+ (self.trailer_lengths.len() * CRLF.len()) as u64
}
pub fn with_stream_length(mut self, stream_length: u64) -> Self {
self.stream_length = stream_length;
self
}
pub fn with_trailer_len(mut self, trailer_len: u64) -> Self {
self.trailer_lengths.push(trailer_len);
self
}
pub fn is_trailer_empty(&self) -> bool {
self.trailer_lengths.is_empty()
}
pub fn disable_chunked_encoding() -> Self {
Self {
disabled: true,
..Default::default()
}
}
pub fn disabled(&self) -> bool {
self.disabled
}
pub fn signed_chunked_encoding(mut self, is_signed: bool) -> Self {
self.is_signed = is_signed;
self
}
pub fn encoded_length(&self) -> u64 {
if self.is_signed {
self.signed_encoded_length()
} else {
self.unsigned_encoded_length()
}
}
fn signed_encoded_length(&self) -> u64 {
let number_of_data_chunks = self.stream_length / self.chunk_size() as u64;
let remaining_data_chunk = self.stream_length % self.chunk_size() as u64;
let mut length = number_of_data_chunks
* get_signed_chunk_bytes_length(self.chunk_size() as u64)
+ if remaining_data_chunk > 0 {
get_signed_chunk_bytes_length(remaining_data_chunk)
} else {
0
};
length += get_signed_chunk_bytes_length(0);
length -= CRLF.len() as u64;
for len in self.trailer_lengths.iter() {
length += len + CRLF.len() as u64;
}
length += CRLF.len() as u64;
length
}
fn unsigned_encoded_length(&self) -> u64 {
let number_of_data_chunks = self.stream_length / self.chunk_size() as u64;
let remaining_data_chunk = self.stream_length % self.chunk_size() as u64;
let mut length = number_of_data_chunks
* get_unsigned_chunk_bytes_length(self.chunk_size() as u64)
+ if remaining_data_chunk > 0 {
get_unsigned_chunk_bytes_length(remaining_data_chunk)
} else {
0
};
length += CHUNK_TERMINATOR.len() as u64;
for len in self.trailer_lengths.iter() {
length += len + CRLF.len() as u64;
}
length += CRLF.len() as u64;
length
}
}
fn int_log16<T>(mut i: T) -> u64
where
T: std::ops::DivAssign + PartialOrd + From<u8> + Copy,
{
let mut len = 0;
let zero = T::from(0);
let sixteen = T::from(16);
if i == zero {
return 1;
}
while i > zero {
i /= sixteen;
len += 1;
}
len
}
fn get_signed_chunk_bytes_length(payload_length: u64) -> u64 {
let hex_repr_len = int_log16(payload_length);
hex_repr_len
+ CHUNK_SIGNATURE_BEGIN.len() as u64
+ SIGNATURE_LENGTH as u64
+ CRLF.len() as u64
+ payload_length
+ CRLF.len() as u64
}
fn get_unsigned_chunk_bytes_length(payload_length: u64) -> u64 {
let hex_repr_len = int_log16(payload_length);
hex_repr_len + CRLF.len() as u64 + payload_length + CRLF.len() as u64
}
#[cfg(test)]
mod tests {
use super::int_log16;
#[test]
fn test_int_log16() {
assert_eq!(int_log16(0u64), 1); assert_eq!(int_log16(1u64), 1); assert_eq!(int_log16(15u64), 1); assert_eq!(int_log16(16u64), 2); assert_eq!(int_log16(255u64), 2); assert_eq!(int_log16(256u64), 3); assert_eq!(int_log16(4095u64), 3); assert_eq!(int_log16(4096u64), 4); assert_eq!(int_log16(65535u64), 4); assert_eq!(int_log16(65536u64), 5); assert_eq!(int_log16(1048575u64), 5); assert_eq!(int_log16(1048576u64), 6); assert_eq!(int_log16(u64::MAX), 16); }
}