use kawa::{AsBuffer, Kawa};
const INLINE_CAPACITY: usize = 128;
const RETAINED_CAPACITY: usize = 1024;
#[derive(Debug)]
pub struct H2Output {
inline: [u8; INLINE_CAPACITY],
end: usize,
heap: Vec<u8>,
spilled: bool,
head: usize,
prepaid: usize,
}
impl Default for H2Output {
fn default() -> Self {
Self {
inline: [0; INLINE_CAPACITY],
end: 0,
heap: Vec::new(),
spilled: false,
head: 0,
prepaid: 0,
}
}
}
impl H2Output {
pub fn pending(&self) -> &[u8] {
if self.spilled {
&self.heap[self.head..]
} else {
&self.inline[self.head..self.end]
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
self.stored() - self.head
}
fn stored(&self) -> usize {
if self.spilled {
self.heap.len()
} else {
self.end
}
}
fn grow(&mut self, room: usize) -> usize {
if !self.spilled {
if self.end + room > INLINE_CAPACITY && self.head > 0 {
self.inline.copy_within(self.head..self.end, 0);
self.end -= self.head;
self.head = 0;
}
if self.end + room <= INLINE_CAPACITY {
let start = self.end;
self.end += room;
return start;
}
self.heap.clear();
self.heap.reserve(self.end + room);
self.heap.extend_from_slice(&self.inline[..self.end]);
self.spilled = true;
}
let start = self.heap.len();
self.heap.resize(start + room, 0);
start
}
fn truncate(&mut self, stored: usize) {
if self.spilled {
self.heap.truncate(stored);
} else {
self.end = stored;
}
}
fn storage_mut(&mut self) -> &mut [u8] {
if self.spilled {
&mut self.heap
} else {
&mut self.inline
}
}
pub fn push_frames<E>(
&mut self,
room: usize,
write: impl FnOnce(&mut [u8]) -> Result<usize, E>,
) -> Result<usize, E> {
let start = self.grow(room);
match write(&mut self.storage_mut()[start..start + room]) {
Ok(size) => {
debug_assert!(size <= room, "a frame writer used more room than given");
self.truncate(start + size.min(room));
Ok(size)
}
Err(error) => {
self.truncate(start);
Err(error)
}
}
}
pub fn push(&mut self, frame: &[u8]) {
let start = self.grow(frame.len());
self.storage_mut()[start..start + frame.len()].copy_from_slice(frame);
}
pub fn consume(&mut self, size: usize) -> usize {
debug_assert!(size <= self.len(), "consumed more output than queued");
let size = size.min(self.len());
self.head += size;
let prepaid = size.min(self.prepaid);
self.prepaid -= prepaid;
if self.is_empty() {
self.clear();
} else {
self.compact();
}
size - prepaid
}
fn compact(&mut self) {
if !self.spilled || self.head < RETAINED_CAPACITY || self.head < self.len() {
return;
}
self.heap.drain(..self.head);
self.head = 0;
let len = self.heap.len();
if self.heap.capacity() > RETAINED_CAPACITY && self.heap.capacity() > 4 * len {
self.heap.shrink_to(RETAINED_CAPACITY.max(2 * len));
}
}
pub fn clear(&mut self) {
self.head = 0;
self.end = 0;
self.prepaid = 0;
self.spilled = false;
if self.heap.capacity() > RETAINED_CAPACITY {
self.heap = Vec::new();
} else {
self.heap.clear();
}
}
pub fn adopt_tail<T: AsBuffer>(&mut self, kawa: &mut Kawa<T>, tail: usize) {
debug_assert!(
self.is_empty(),
"a stream frame rest can only be adopted by an empty output queue"
);
let buffer = kawa.storage.buffer();
let mut left = tail;
for block in kawa.out.iter() {
if left == 0 {
break;
}
match block {
kawa::OutBlock::Delimiter => break,
kawa::OutBlock::Store(store) => {
let data = store.data(buffer);
let size = data.len().min(left);
self.push(&data[..size]);
left -= size;
}
}
}
debug_assert_eq!(left, 0, "the frame rest must be queued in `kawa.out`");
let adopted = tail - left;
self.prepaid += adopted;
kawa.consume(adopted);
}
}
#[cfg(test)]
mod tests {
use super::*;
use kawa::{Buffer, Kind, SliceBuffer, Store};
#[test]
fn frames_are_sent_in_the_order_they_were_queued() {
let mut output = H2Output::default();
output.push(b"first");
let queued = output.push_frames(16, |buf| {
buf[..6].copy_from_slice(b"second");
Ok::<_, ()>(6)
});
assert_eq!(queued, Ok(6));
assert_eq!(output.pending(), b"firstsecond");
assert_eq!(output.consume(3), 3);
assert_eq!(output.pending(), b"stsecond");
}
#[test]
fn a_failed_frame_writer_queues_nothing() {
let mut output = H2Output::default();
output.push(b"kept");
assert_eq!(
output.push_frames(16, |_| Err::<usize, _>("full")),
Err("full")
);
assert_eq!(output.pending(), b"kept");
}
#[test]
fn an_adopted_tail_is_prepaid_and_control_bytes_behind_it_are_not() {
let mut storage = [0u8; 64];
let mut kawa = Kawa::new(Kind::Response, Buffer::new(SliceBuffer(&mut storage)));
kawa.push_out(Store::Static(b"0123"));
kawa.push_out(Store::Static(b"456789"));
let mut output = H2Output::default();
output.adopt_tail(&mut kawa, 7);
output.push(b"ACK");
assert_eq!(output.pending(), b"0123456ACK");
assert_eq!(kawa.out.len(), 1, "the stream keeps what was not adopted");
assert_eq!(output.consume(5), 0, "the adopted rest was counted already");
assert_eq!(output.consume(5), 3, "only the ACK is overhead");
assert!(output.is_empty());
}
#[test]
fn a_drained_queue_gives_back_a_large_allocation() {
let mut output = H2Output::default();
output.push(&[0; RETAINED_CAPACITY + 1]);
output.consume(RETAINED_CAPACITY + 1);
assert_eq!(output.heap.capacity(), 0);
assert!(!output.spilled);
}
#[test]
fn a_server_preface_stays_inline() {
let mut output = H2Output::default();
output.push(&[1; 57]);
output.push(&[2; 13]);
output.push(&[3; 9]);
assert!(!output.spilled, "79 bytes must not allocate");
assert_eq!(output.heap.capacity(), 0);
assert_eq!(output.len(), 79);
}
#[test]
fn a_queue_that_never_drains_stays_bounded() {
let mut output = H2Output::default();
let frame = [7u8; 4096];
output.push(&frame);
for _ in 0..1000 {
output.push(&frame);
assert_eq!(output.consume(frame.len()), frame.len());
assert_eq!(output.len(), frame.len(), "one frame always left queued");
}
assert!(
output.head <= RETAINED_CAPACITY.max(output.len())
&& output.heap.capacity() <= 4 * (RETAINED_CAPACITY + 2 * frame.len()),
"the heap must stay bounded while the queue never drains: head {}, capacity {}",
output.head,
output.heap.capacity()
);
assert_eq!(output.pending(), &frame[..], "the queued frame is intact");
}
#[test]
fn a_queue_past_its_inline_capacity_keeps_its_order() {
let mut output = H2Output::default();
let first: Vec<u8> = (0..100).collect();
output.push(&first);
assert_eq!(output.consume(60), 60);
let second: Vec<u8> = (100..180).collect();
output.push(&second);
assert!(!output.spilled, "40 + 80 bytes fit once compacted");
let third: Vec<u8> = (180..=255).collect();
output.push(&third);
assert!(output.spilled, "196 bytes do not fit inline");
let expected: Vec<u8> = (60..=255).collect();
assert_eq!(output.pending(), &expected[..]);
assert_eq!(output.consume(expected.len()), expected.len());
assert!(!output.spilled && output.is_empty());
output.push(b"again");
assert_eq!(output.pending(), b"again");
}
}