use std::collections::VecDeque;
use crate::constants;
use super::flow::{CreditWindow, Violation};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReadOutcome {
Data(usize),
End,
Reset(u64),
}
pub(crate) struct RecvHalf {
reassembly: Reassembly,
read_offset: u64,
high_water: u64,
final_size: Option<u64>,
reset: Option<u64>,
reset_observed: bool,
credit: CreditWindow,
earns_stream_credit: bool,
counted: u64,
}
impl RecvHalf {
pub(crate) fn new() -> Self {
Self::with_window(constants::INITIAL_MAX_STREAM_DATA)
}
pub(crate) fn with_window(window: u64) -> Self {
Self {
reassembly: Reassembly::new(window),
read_offset: 0,
high_water: 0,
final_size: None,
reset: None,
reset_observed: false,
credit: CreditWindow::configured(window, constants::INITIAL_MAX_STREAM_DATA),
earns_stream_credit: true,
counted: 0,
}
}
pub(crate) fn advertised(&self) -> u64 {
self.credit.advertised()
}
pub(crate) fn high_water(&self) -> u64 {
self.high_water
}
pub(crate) fn final_size(&self) -> Option<u64> {
self.final_size
}
pub(crate) fn counted(&self) -> u64 {
self.counted
}
pub(crate) fn capacity(&self) -> u64 {
self.reassembly.capacity()
}
pub(crate) fn copy_work(&self) -> u64 {
self.reassembly.copy_work()
}
pub(crate) fn is_readable(&self) -> bool {
self.reset.is_some()
|| self.reassembly.contiguous_at(self.read_offset) > 0
|| self.final_size == Some(self.read_offset)
}
pub(crate) fn is_retired(&self) -> bool {
self.reset_observed || self.final_size == Some(self.read_offset)
}
pub(crate) fn is_complete(&self) -> bool {
self.reset.is_none()
&& self.final_size.is_some_and(|size| {
self.read_offset + self.reassembly.contiguous_at(self.read_offset) == size
})
}
pub(crate) fn take_message(&mut self) -> Option<Vec<u8>> {
if !self.is_complete() {
return None;
}
let size = self.final_size?;
let remaining = usize::try_from(size - self.read_offset).ok()?;
let mut buf = vec![0u8; remaining];
let got = self.reassembly.read(self.read_offset, &mut buf);
debug_assert_eq!(
got, remaining,
"a complete half holds every byte to its final size"
);
buf.truncate(got);
self.read_offset += got as u64;
self.credit.consume(got as u64);
self.counted = self.counted.saturating_add(got as u64);
Some(buf)
}
pub(crate) fn check_stream(&self, offset: u64, len: u64, fin: bool) -> Result<u64, Violation> {
let end = offset.checked_add(len).ok_or(Violation::FinalSize)?;
if self
.final_size
.is_some_and(|pinned| end > pinned || (fin && end != pinned))
{
return Err(Violation::FinalSize);
}
if fin && end < self.high_water {
return Err(Violation::FinalSize);
}
let new_high = self.high_water.max(end);
if new_high > self.credit.advertised() {
return Err(Violation::FlowControl);
}
Ok(new_high)
}
pub(crate) fn check_reset(&self, final_size: u64) -> Result<(), Violation> {
if final_size < self.high_water {
return Err(Violation::FinalSize);
}
if self.final_size.is_some_and(|pinned| pinned != final_size) {
return Err(Violation::FinalSize);
}
if final_size > self.credit.advertised() {
return Err(Violation::FlowControl);
}
Ok(())
}
pub(crate) fn apply_stream(
&mut self,
offset: u64,
data: &[u8],
fin: bool,
) -> Result<u64, Violation> {
let end = offset.saturating_add(data.len() as u64);
if self.reset.is_none() {
self.reassembly.insert(offset, data, self.read_offset)?;
}
if fin {
self.final_size = Some(end);
}
let delta = end.saturating_sub(self.high_water);
self.high_water = self.high_water.max(end);
Ok(delta)
}
pub(crate) fn apply_reset(&mut self, final_size: u64, code: u64) -> (u64, bool) {
if self.final_size == Some(final_size) && self.high_water == final_size {
return (0, false);
}
let delta = final_size.saturating_sub(self.high_water);
self.high_water = final_size;
self.final_size = Some(final_size);
let newly = self.reset.is_none();
if newly {
self.reset = Some(code);
}
self.reassembly.discard();
(delta, newly)
}
pub(crate) fn read(&mut self, buf: &mut [u8]) -> (ReadOutcome, u64) {
if let Some(code) = self.reset {
self.reset_observed = true;
return (ReadOutcome::Reset(code), 0);
}
let n = self.reassembly.read(self.read_offset, buf);
if n > 0 {
self.read_offset += n as u64;
self.credit.consume(n as u64);
self.counted = self.counted.saturating_add(n as u64);
return (ReadOutcome::Data(n), n as u64);
}
if self.final_size == Some(self.read_offset) {
(ReadOutcome::End, 0)
} else {
(ReadOutcome::Data(0), 0)
}
}
pub(crate) fn take_grant(&mut self) -> Option<u64> {
if !self.earns_stream_credit {
return None;
}
self.credit.take_grant()
}
pub(crate) fn take_initial_grant(&mut self) -> bool {
if !self.earns_stream_credit {
return false;
}
self.credit.take_announcement()
}
pub(crate) fn retirement_target(&self) -> u64 {
match self.final_size {
Some(size) => size,
None => self.credit.advertised(),
}
}
pub(crate) fn tombstone(&self) -> RecvTombstone {
RecvTombstone {
limit: self.credit.advertised(),
high_water: self.high_water,
final_size: self.final_size,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct RecvTombstone {
limit: u64,
high_water: u64,
final_size: Option<u64>,
}
impl RecvTombstone {
pub(crate) fn check_stream(&self, offset: u64, len: u64, fin: bool) -> Result<(), Violation> {
let end = offset.checked_add(len).ok_or(Violation::FinalSize)?;
if self
.final_size
.is_some_and(|pinned| end > pinned || (fin && end != pinned))
{
return Err(Violation::FinalSize);
}
if fin && end < self.high_water {
return Err(Violation::FinalSize);
}
if end > self.limit {
return Err(Violation::FlowControl);
}
Ok(())
}
pub(crate) fn check_reset(&self, final_size: u64) -> Result<(), Violation> {
if final_size < self.high_water {
return Err(Violation::FinalSize);
}
if self.final_size.is_some_and(|pinned| pinned != final_size) {
return Err(Violation::FinalSize);
}
if final_size > self.limit {
return Err(Violation::FlowControl);
}
Ok(())
}
}
struct Chunk {
offset: u64,
data: Vec<u8>,
head: usize,
}
const REASSEMBLY_SLACK_SHIFT: u32 = 3;
impl Chunk {
fn from_frame(offset: u64, data: &[u8]) -> Self {
Self {
offset,
data: data.to_vec(),
head: 0,
}
}
fn vacant() -> Self {
Self {
offset: 0,
data: Vec::new(),
head: 0,
}
}
fn len(&self) -> usize {
self.data.len() - self.head
}
fn end(&self) -> u64 {
self.offset + self.len() as u64
}
fn bytes(&self) -> &[u8] {
&self.data[self.head..]
}
fn at_mut(&mut self, at: u64, n: usize) -> &mut [u8] {
let from = self.head + (at - self.offset) as usize;
&mut self.data[from..from + n]
}
fn write_frame(&mut self, from: u64, to: u64, frame_at: u64, frame: &[u8]) -> u64 {
debug_assert!(from >= frame_at && to <= frame_at + frame.len() as u64);
let src = &frame[(from - frame_at) as usize..(to - frame_at) as usize];
self.at_mut(from, src.len()).copy_from_slice(src);
src.len() as u64
}
fn write_chunk(&mut self, other: &Chunk) -> u64 {
let n = other.len();
self.at_mut(other.offset, n).copy_from_slice(other.bytes());
n as u64
}
fn reserve_span(&mut self, start: u64, stop: u64) -> u64 {
debug_assert!(start <= self.offset && stop >= self.end());
let front = (self.offset - start) as usize;
let back = (stop - self.end()) as usize;
if front <= self.head && back <= self.data.capacity() - self.data.len() {
self.head -= front;
self.data[self.head..self.head + front].fill(0);
self.data.resize(self.data.len() + back, 0);
self.offset = start;
return 0;
}
let held = self.len();
let need = front + held + back;
let slack = need >> REASSEMBLY_SLACK_SHIFT;
let (gap_front, gap_back) = match (front > 0, back > 0) {
(true, true) => (slack / 2, slack - slack / 2),
(true, false) => (slack, 0),
_ => (0, slack),
};
let mut fresh = Vec::with_capacity(gap_front + need + gap_back);
fresh.resize(gap_front + front, 0);
fresh.extend_from_slice(self.bytes());
fresh.resize(gap_front + need, 0);
self.data = fresh;
self.head = gap_front;
self.offset = start;
held as u64
}
fn advance(&mut self, n: usize) {
self.head += n;
self.offset += n as u64;
}
}
fn ceiling_for(window: u64) -> usize {
let derived = window / constants::REASSEMBLY_MIN_CONFORMING_FRAME + 1;
let derived = usize::try_from(derived).unwrap_or(usize::MAX);
derived.max(constants::REASSEMBLY_CHUNKS_MAX)
}
struct Reassembly {
chunks: VecDeque<Chunk>,
copy_work: u64,
chunks_max: usize,
}
impl Reassembly {
fn new(window: u64) -> Self {
Self {
chunks: VecDeque::new(),
copy_work: 0,
chunks_max: ceiling_for(window),
}
}
fn capacity(&self) -> u64 {
self.chunks.iter().map(|c| c.data.capacity() as u64).sum()
}
fn copy_work(&self) -> u64 {
self.copy_work
}
fn contiguous_at(&self, at: u64) -> u64 {
match self.chunks.front() {
Some(c) if c.offset == at => c.len() as u64,
_ => 0,
}
}
fn discard(&mut self) {
self.chunks = VecDeque::new();
}
fn insert(
&mut self,
mut offset: u64,
mut data: &[u8],
read_offset: u64,
) -> Result<(), Violation> {
if offset < read_offset {
let skip = read_offset - offset;
if skip >= data.len() as u64 {
return Ok(());
}
data = &data[skip as usize..];
offset = read_offset;
}
if data.is_empty() {
return Ok(());
}
let end = offset + data.len() as u64;
let mut lo = 0usize;
while lo < self.chunks.len() && self.chunks[lo].end() < offset {
lo += 1;
}
if self
.chunks
.get(lo)
.is_some_and(|c| c.offset <= offset && end <= c.end())
{
return Ok(());
}
let mut hi = lo;
while hi < self.chunks.len() && self.chunks[hi].offset <= end {
hi += 1;
}
if lo == hi {
self.chunks.insert(lo, Chunk::from_frame(offset, data));
self.copy_work += data.len() as u64;
} else {
let start = self.chunks[lo].offset.min(offset);
let stop = self.chunks[hi - 1].end().max(end);
let base_at = (lo..hi)
.max_by_key(|&k| self.chunks[k].len())
.expect("lo < hi");
let mut base = std::mem::replace(&mut self.chunks[base_at], Chunk::vacant());
let (base_off, base_end) = (base.offset, base.end());
let mut work = base.reserve_span(start, stop);
let mut cur = start;
for c in self.chunks.range(lo..base_at) {
if cur < c.offset {
work += base.write_frame(cur, c.offset, offset, data);
}
work += base.write_chunk(c);
cur = c.end();
}
if cur < base_off {
work += base.write_frame(cur, base_off, offset, data);
}
let mut cur = base_end;
for c in self.chunks.range(base_at + 1..hi) {
if cur < c.offset {
work += base.write_frame(cur, c.offset, offset, data);
}
work += base.write_chunk(c);
cur = c.end();
}
if cur < stop {
work += base.write_frame(cur, stop, offset, data);
}
self.copy_work += work;
self.chunks[base_at] = base;
for _ in lo..base_at {
self.chunks.remove(lo);
}
for _ in base_at + 1..hi {
self.chunks.remove(lo + 1);
}
}
if self.chunks.len() > self.chunks_max {
return Err(Violation::Reassembly);
}
Ok(())
}
fn read(&mut self, at: u64, buf: &mut [u8]) -> usize {
let Some(front) = self.chunks.front_mut() else {
return 0;
};
if front.offset != at {
return 0;
}
let n = buf.len().min(front.len());
buf[..n].copy_from_slice(&front.bytes()[..n]);
front.advance(n);
if front.len() == 0 {
self.chunks.pop_front();
}
n
}
}
#[cfg(test)]
mod tests {
use super::*;
fn drain(half: &mut RecvHalf) -> Vec<u8> {
let mut out = Vec::new();
let mut buf = [0u8; 64];
loop {
match half.read(&mut buf).0 {
ReadOutcome::Data(0) | ReadOutcome::End => return out,
ReadOutcome::Data(n) => out.extend_from_slice(&buf[..n]),
ReadOutcome::Reset(_) => return out,
}
}
}
#[test]
fn out_of_order_ranges_reassemble() {
let mut half = RecvHalf::new();
half.apply_stream(6, b"world", false).unwrap();
assert_eq!(drain(&mut half), b"", "nothing is contiguous yet");
half.apply_stream(0, b"hello ", false).unwrap();
assert_eq!(drain(&mut half), b"hello world");
}
#[test]
fn overlapping_ranges_deliver_each_byte_once() {
let mut half = RecvHalf::new();
half.apply_stream(0, b"abcdef", false).unwrap();
half.apply_stream(3, b"defghi", false).unwrap();
half.apply_stream(2, b"cd", false).unwrap();
assert_eq!(drain(&mut half), b"abcdefghi");
}
#[test]
fn a_fully_read_range_that_arrives_again_stores_nothing() {
let mut half = RecvHalf::new();
half.apply_stream(0, b"abcdef", false).unwrap();
assert_eq!(drain(&mut half), b"abcdef");
assert_eq!(half.capacity(), 0);
half.apply_stream(0, b"abcdef", false).unwrap();
assert_eq!(half.capacity(), 0);
assert_eq!(drain(&mut half), b"");
}
#[test]
fn capacity_is_what_arrived_and_not_the_window() {
let mut half = RecvHalf::new();
assert_eq!(half.capacity(), 0);
half.apply_stream(0, &[0u8; 100], false).unwrap();
assert_eq!(half.capacity(), 100);
assert!(half.capacity() < constants::INITIAL_MAX_STREAM_DATA);
let mut buf = [0u8; 100];
assert_eq!(half.read(&mut buf).0, ReadOutcome::Data(100));
assert_eq!(half.capacity(), 0);
}
#[test]
fn the_reassembly_chunk_ceiling_is_two_sided() {
let mut half = RecvHalf::new();
for i in 0..constants::REASSEMBLY_CHUNKS_MAX as u64 {
half.apply_stream(i * 2, b"x", false)
.expect("1024 ranges is inside the ceiling");
}
let n = constants::REASSEMBLY_CHUNKS_MAX as u64;
assert_eq!(
half.apply_stream(n * 2, b"x", false),
Err(Violation::Reassembly)
);
}
#[test]
fn adjacent_ranges_coalesce_rather_than_accumulate() {
let mut half = RecvHalf::new();
for i in 0..2_000u64 {
half.apply_stream(i, b"x", false)
.expect("adjacent ranges coalesce into one");
}
assert_eq!(half.capacity(), 2_104);
}
#[test]
fn capped_growth_holds_capacity_within_an_eighth_of_the_span() {
let mut half = RecvHalf::new();
for i in 0..3_000u64 {
half.apply_stream(i, b"x", false)
.expect("inside the window");
let span = i + 1;
assert!(
half.capacity() <= span + span / 8,
"capacity {} exceeds an eighth over the {span}-byte span",
half.capacity(),
);
}
}
#[test]
fn an_appending_bridge_copies_the_new_byte_and_not_the_range_it_extends() {
let mut half = RecvHalf::new();
half.apply_stream(0, &[7u8; 1_000], false).unwrap();
assert_eq!(half.copy_work(), 1_000, "the arriving bytes, once");
for i in 0..100u64 {
half.apply_stream(1_000 + i, b"x", false).unwrap();
}
assert_eq!(half.copy_work(), 2_100);
assert_eq!(half.capacity(), 1_126);
assert_eq!(drain(&mut half).len(), 1_100, "and the bytes are all there");
}
#[test]
fn a_prepending_bridge_copies_the_new_byte_too() {
let mut half = RecvHalf::new();
half.apply_stream(1_000, &[7u8; 1_000], false).unwrap();
for i in 1..=100u64 {
half.apply_stream(1_000 - i, b"x", false).unwrap();
}
assert_eq!(half.copy_work(), 2_100);
assert_eq!(half.capacity(), 1_126);
assert_eq!(
drain(&mut half).len(),
0,
"byte 0 never arrived, so nothing is contiguous — which is what \
makes this the attack and not a transfer"
);
}
#[test]
fn a_reset_returns_the_capacity_and_not_the_copy_work() {
let mut half = RecvHalf::new();
half.apply_stream(0, &[7u8; 200], false).unwrap();
assert_eq!(half.capacity(), 200);
assert_eq!(half.copy_work(), 200);
half.apply_reset(500, 42);
assert_eq!(half.capacity(), 0, "§12.7: the discard is the first moment");
assert_eq!(
half.copy_work(),
200,
"a peer that resets has still spent the work"
);
}
#[test]
fn a_covered_frame_writes_nothing() {
let mut half = RecvHalf::new();
half.apply_stream(0, b"abcdef", false).unwrap();
let (cap, work) = (half.capacity(), half.copy_work());
half.apply_stream(2, b"cd", false).unwrap();
half.apply_stream(0, b"abcdef", false).unwrap();
half.apply_stream(5, b"f", false).unwrap();
assert_eq!(half.capacity(), cap, "no allocation");
assert_eq!(half.copy_work(), work, "and no copy");
assert_eq!(drain(&mut half), b"abcdef");
}
#[test]
fn the_three_final_size_errors() {
let mut half = RecvHalf::new();
half.apply_stream(0, b"abcde", true).unwrap();
assert_eq!(half.check_stream(5, 1, false), Err(Violation::FinalSize));
assert_eq!(half.check_stream(0, 3, true), Err(Violation::FinalSize));
assert_eq!(half.check_stream(0, 5, true), Ok(5));
let mut half = RecvHalf::new();
half.apply_stream(0, b"abcdef", false).unwrap();
assert_eq!(half.check_stream(0, 3, true), Err(Violation::FinalSize));
}
#[test]
fn the_stream_credit_bound_is_two_sided() {
let half = RecvHalf::new();
let window = constants::INITIAL_MAX_STREAM_DATA;
assert!(half.check_stream(window - 1, 1, false).is_ok());
assert_eq!(
half.check_stream(window, 1, false),
Err(Violation::FlowControl)
);
assert_eq!(
half.check_stream(u64::MAX, 2, false),
Err(Violation::FinalSize)
);
}
#[test]
fn a_reset_discards_the_buffer_and_surfaces_once_observed() {
let mut half = RecvHalf::new();
half.apply_stream(0, &[7u8; 200], false).unwrap();
assert_eq!(half.capacity(), 200);
let (delta, newly) = half.apply_reset(500, 42);
assert!(newly);
assert_eq!(delta, 300);
assert_eq!(half.capacity(), 0, "§12.7: the discard is the first moment");
let mut buf = [0u8; 8];
assert_eq!(half.read(&mut buf).0, ReadOutcome::Reset(42));
assert!(half.is_retired());
}
#[test]
fn a_reset_agreeing_with_a_complete_half_is_a_no_op() {
let mut half = RecvHalf::new();
half.apply_stream(0, b"abcde", true).unwrap();
assert_eq!(half.check_reset(5), Ok(()));
let (delta, newly) = half.apply_reset(5, 9);
assert_eq!(delta, 0);
assert!(!newly);
assert_eq!(drain(&mut half), b"abcde");
}
#[test]
fn end_of_stream_follows_the_last_byte_rather_than_replacing_it() {
let mut half = RecvHalf::new();
half.apply_stream(0, b"ab", true).unwrap();
let mut buf = [0u8; 8];
assert_eq!(half.read(&mut buf).0, ReadOutcome::Data(2));
assert!(half.is_retired());
assert_eq!(half.read(&mut buf).0, ReadOutcome::End);
}
#[test]
fn an_empty_open_half_parks_rather_than_ending() {
let mut half = RecvHalf::new();
let mut buf = [0u8; 8];
assert_eq!(half.read(&mut buf).0, ReadOutcome::Data(0));
assert!(!half.is_retired());
}
#[test]
fn the_retirement_target_is_the_advertised_limit_not_the_high_water_mark() {
let mut half = RecvHalf::new();
half.apply_stream(0, &[0u8; 1_000], false).unwrap();
assert_eq!(half.high_water(), 1_000);
assert_eq!(
half.retirement_target(),
constants::INITIAL_MAX_STREAM_DATA,
"the high-water mark leaks credit permanently"
);
let mut buf = vec![0u8; constants::INITIAL_MAX_STREAM_DATA as usize];
for _ in 0..8 {
half.apply_stream(half.high_water(), &[0u8; 20_000], false)
.unwrap();
while let ReadOutcome::Data(n) = half.read(&mut buf).0 {
if n == 0 {
break;
}
}
half.take_grant();
}
assert!(half.retirement_target() > constants::INITIAL_MAX_STREAM_DATA);
assert!(half.retirement_target() > half.high_water());
}
#[test]
fn a_pinned_final_size_is_the_retirement_target() {
let mut half = RecvHalf::new();
half.apply_stream(0, b"abcde", true).unwrap();
assert_eq!(half.retirement_target(), 5);
}
}