pub use crate::event::ReassemblyStop;
use crate::{
Timestamp,
event::{FlowSide, OverflowPolicy},
tracker::FlowTrackerConfig,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct GapMark {
at: usize,
len: u64,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct StreamChunks {
bytes: Vec<u8>,
gaps: Vec<GapMark>,
stop: Option<ReassemblyStop>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Chunk<'a> {
Data(&'a [u8]),
Gap(u64),
}
impl StreamChunks {
pub const fn new() -> Self {
Self {
bytes: Vec::new(),
gaps: Vec::new(),
stop: None,
}
}
pub fn clear(&mut self) {
self.bytes.clear();
self.gaps.clear();
self.stop = None;
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty() && self.gaps.is_empty() && self.stop.is_none()
}
pub fn len(&self) -> usize {
self.bytes.len()
}
pub fn push_data(&mut self, data: &[u8]) {
self.bytes.extend_from_slice(data);
}
pub(crate) fn push_owned(&mut self, data: Vec<u8>) {
if self.bytes.is_empty() {
self.bytes = data;
} else {
self.bytes.extend_from_slice(&data);
}
}
pub(crate) fn is_empty_data(&self) -> bool {
self.bytes.is_empty()
}
pub fn push_gap(&mut self, len: u64) {
if len == 0 {
return;
}
let at = self.bytes.len();
match self.gaps.last_mut() {
Some(last) if last.at == at => last.len = last.len.saturating_add(len),
_ => self.gaps.push(GapMark { at, len }),
}
}
pub fn set_stop(&mut self, stop: ReassemblyStop) {
self.stop.get_or_insert(stop);
}
pub fn stop(&self) -> Option<ReassemblyStop> {
self.stop
}
pub fn data(&self) -> &[u8] {
&self.bytes
}
pub fn gap_count(&self) -> usize {
self.gaps.len()
}
pub fn gap_bytes(&self) -> u64 {
self.gaps.iter().map(|g| g.len).sum()
}
pub fn iter(&self) -> Chunks<'_> {
Chunks {
chunks: self,
pos: 0,
gap_idx: 0,
}
}
pub fn append(&mut self, other: &mut StreamChunks) {
if self.bytes.is_empty() && self.gaps.is_empty() {
self.bytes = std::mem::take(&mut other.bytes);
self.gaps = std::mem::take(&mut other.gaps);
} else {
let base = self.bytes.len();
for g in other.gaps.drain(..) {
let at = base + g.at;
match self.gaps.last_mut() {
Some(last) if last.at == at => last.len = last.len.saturating_add(g.len),
_ => self.gaps.push(GapMark { at, len: g.len }),
}
}
self.bytes.extend_from_slice(&other.bytes);
other.release();
}
if let Some(stop) = other.stop.take() {
self.set_stop(stop);
}
}
pub(crate) fn drop_front(&mut self, n: usize) {
let n = n.min(self.bytes.len());
if n == 0 {
return;
}
self.bytes.drain(..n);
let mut front = n as u64;
self.gaps.retain_mut(|g| {
if g.at <= n {
front = front.saturating_add(g.len);
false
} else {
g.at -= n;
true
}
});
self.gaps.insert(0, GapMark { at: 0, len: front });
}
pub(crate) fn take_bytes(&mut self) -> Vec<u8> {
self.gaps.clear();
self.stop = None;
std::mem::take(&mut self.bytes)
}
pub(crate) fn release(&mut self) {
self.bytes = Vec::new();
self.gaps = Vec::new();
}
}
pub struct Chunks<'a> {
chunks: &'a StreamChunks,
pos: usize,
gap_idx: usize,
}
impl<'a> Iterator for Chunks<'a> {
type Item = Chunk<'a>;
fn next(&mut self) -> Option<Chunk<'a>> {
let c = self.chunks;
if let Some(g) = c.gaps.get(self.gap_idx)
&& g.at == self.pos
{
self.gap_idx += 1;
return Some(Chunk::Gap(g.len));
}
if self.pos >= c.bytes.len() {
return None;
}
let end = c
.gaps
.get(self.gap_idx)
.map_or(c.bytes.len(), |g| g.at.min(c.bytes.len()));
let data = &c.bytes[self.pos..end];
self.pos = end;
Some(Chunk::Data(data))
}
}
pub trait Reassembler: Send + 'static {
fn segment(&mut self, seq: u32, payload: &[u8], ts: Timestamp);
fn segment_into(&mut self, seq: u32, payload: &[u8], ts: Timestamp) -> SegmentOutcome {
self.segment(seq, payload, ts);
SegmentOutcome::Buffered
}
fn set_origin(&mut self, _seq: u32) {}
fn peer_ack(&mut self, _ack: u32, _ts: Timestamp) {}
fn fin_seen(&mut self, _end: u32, _ts: Timestamp) {}
fn drain_into(&mut self, _out: &mut StreamChunks) {}
fn flush_pending(&mut self) {}
fn advance_time(&mut self, _now: Timestamp) {}
fn fin(&mut self) {}
fn rst(&mut self) {}
fn dropped_segments(&self) -> u64 {
0
}
fn gaps(&self) -> u64 {
0
}
fn gap_bytes(&self) -> u64 {
0
}
fn bytes_dropped_oversize(&self) -> u64 {
0
}
fn stop_reason(&self) -> Option<ReassemblyStop> {
None
}
fn high_watermark(&self) -> u64 {
0
}
fn bytes_in_flight(&self) -> u64 {
0
}
fn high_watermark_crossings(&self) -> u64 {
0
}
fn high_watermark_threshold(&self) -> Option<(u64, u8)> {
None
}
fn retransmits(&self) -> u64 {
0
}
fn on_duplicate(&mut self, _seq: u32, _payload: &[u8], _ts: Timestamp) {}
fn rexmit_inconsistencies(&self) -> u64 {
0
}
fn release(&mut self) {}
fn current_bytes(&self) -> u64 {
0
}
fn out_of_window_segments(&self) -> u64 {
0
}
fn ack_confirmed_gaps(&self) -> u64 {
0
}
fn origin_resets(&self) -> u64 {
0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SegmentOutcome {
Buffered,
Passthrough {
skip: usize,
},
}
pub trait ReassemblerFactory<K>: Send + 'static {
type Reassembler: Reassembler;
fn new_reassembler(&mut self, key: &K, side: FlowSide) -> Self::Reassembler;
fn apply_config(&mut self, _config: &FlowTrackerConfig) {}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopReassembler;
impl Reassembler for NoopReassembler {
fn segment(&mut self, _seq: u32, _payload: &[u8], _ts: Timestamp) {}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopReassemblerFactory;
impl<K> ReassemblerFactory<K> for NoopReassemblerFactory {
type Reassembler = NoopReassembler;
fn new_reassembler(&mut self, _key: &K, _side: FlowSide) -> NoopReassembler {
NoopReassembler
}
}
pub use crate::tracker::{DEFAULT_ACK_GRACE, DEFAULT_MAX_AHEAD};
pub const DEFAULT_REORDER_DEADLINE: std::time::Duration = std::time::Duration::from_secs(1);
const SKIPPED_HISTORY: usize = 8;
#[derive(Debug)]
pub(crate) struct Candidate {
pub(crate) off: u64,
pub(crate) data: Vec<u8>,
pub(crate) ts: Timestamp,
}
pub(crate) enum FarOutcome {
Held,
Resync(Box<Candidate>),
}
#[derive(Debug, Default)]
pub(crate) struct Track {
anchored: bool,
next_seq: u32,
pub(crate) next_off: u64,
evidence: u64,
evidence_since: Option<Timestamp>,
fin_off: Option<u64>,
oow: Option<Box<Candidate>>,
skipped: [(u64, u64); SKIPPED_HISTORY],
skipped_len: usize,
skipped_head: usize,
pub(crate) last_progress: Timestamp,
pub(crate) out_of_window: u64,
pub(crate) origin_resets: u64,
}
impl Track {
#[cfg(test)]
pub(crate) fn anchored(&self) -> bool {
self.anchored
}
pub(crate) fn anchor(&mut self, seq: u32, ts: Timestamp) {
if !self.anchored {
self.anchored = true;
self.next_seq = seq;
self.last_progress = ts;
}
}
pub(crate) fn seq_of(&self, off: u64) -> u32 {
self.next_seq
.wrapping_add(off.wrapping_sub(self.next_off) as u32)
}
pub(crate) fn offset_of(&self, seq: u32) -> i64 {
self.next_off as i64 + i64::from(seq.wrapping_sub(self.next_seq) as i32)
}
pub(crate) fn advance_to(&mut self, off: u64, ts: Timestamp) {
if off <= self.next_off {
return;
}
let delta = off - self.next_off;
self.next_off = off;
self.next_seq = self.next_seq.wrapping_add(delta as u32);
self.last_progress = self.last_progress.max(ts);
if self.evidence <= self.next_off {
self.evidence_since = None;
}
}
pub(crate) fn record_skip(&mut self, from: u64, to: u64) {
let i = (self.skipped_head + self.skipped_len) % SKIPPED_HISTORY;
self.skipped[i] = (from, to);
if self.skipped_len < SKIPPED_HISTORY {
self.skipped_len += 1;
} else {
self.skipped_head = (self.skipped_head + 1) % SKIPPED_HISTORY;
}
}
pub(crate) fn in_skipped(&self, start: u64, end: u64) -> bool {
(0..self.skipped_len).any(|i| {
let (f, t) = self.skipped[(self.skipped_head + i) % SKIPPED_HISTORY];
start < t && f < end
})
}
pub(crate) fn far_ahead(
&mut self,
off: u64,
payload: &[u8],
ts: Timestamp,
max_ahead: u64,
) -> FarOutcome {
if let Some(c) = &self.oow
&& off.abs_diff(c.off) <= max_ahead
{
self.out_of_window -= 1;
self.origin_resets += 1;
return FarOutcome::Resync(self.oow.take().expect("checked"));
}
self.out_of_window += 1;
self.oow = Some(Box::new(Candidate {
off,
data: payload.to_vec(),
ts,
}));
FarOutcome::Held
}
pub(crate) fn evidence(
&mut self,
seq: u32,
ts: Timestamp,
max_ahead: u64,
) -> Option<Box<Candidate>> {
if !self.anchored {
return None;
}
let off = self.offset_of(seq);
if off <= self.next_off as i64 {
return None;
}
let mut off = off as u64;
if let Some(fin) = self.fin_off {
off = off.min(fin);
}
if off - self.next_off > max_ahead {
if let Some(c) = &self.oow
&& off >= c.off
{
self.out_of_window -= 1;
self.origin_resets += 1;
return self.oow.take();
}
return None;
}
if off > self.evidence {
self.evidence = off;
}
if self.evidence > self.next_off {
self.evidence_since.get_or_insert(ts);
}
None
}
pub(crate) fn fin(
&mut self,
seq: u32,
ts: Timestamp,
max_ahead: u64,
) -> Option<Box<Candidate>> {
if !self.anchored {
return None;
}
let off = self.offset_of(seq);
if off < self.next_off as i64 {
return None;
}
self.fin_off = Some(off as u64);
if self.evidence > off as u64 {
self.evidence = off as u64;
}
self.evidence(seq, ts, max_ahead)
}
pub(crate) fn evidence_due(&self, now: Timestamp, grace: std::time::Duration) -> Option<u64> {
let since = self.evidence_since?;
(self.evidence > self.next_off && now.saturating_sub(since) >= grace)
.then_some(self.evidence)
}
pub(crate) fn evidence_frontier(&self) -> Option<u64> {
(self.evidence > self.next_off).then_some(self.evidence)
}
pub(crate) fn expire_stray(&mut self, now: Timestamp, max_age: std::time::Duration) {
if self
.oow
.as_ref()
.is_some_and(|c| now.saturating_sub(c.ts) > max_age)
{
self.oow = None;
}
}
pub(crate) fn drop_stray(&mut self) {
self.oow = None;
}
pub(crate) fn heap_bytes(&self) -> usize {
self.oow
.as_ref()
.map_or(0, |c| std::mem::size_of::<Candidate>() + c.data.capacity())
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct StreamRules {
pub(crate) max_ahead: u64,
pub(crate) ack_grace: std::time::Duration,
pub(crate) deadline: std::time::Duration,
}
impl Default for StreamRules {
fn default() -> Self {
Self {
max_ahead: DEFAULT_MAX_AHEAD,
ack_grace: DEFAULT_ACK_GRACE,
deadline: DEFAULT_REORDER_DEADLINE,
}
}
}
#[derive(Debug, Default)]
pub struct BufferedReassembler {
ready: StreamChunks,
track: Track,
rules: StreamRules,
reorder: Option<Box<Candidate>>,
dropped_segments: u64,
gaps: u64,
gap_bytes: u64,
ack_confirmed_gaps: u64,
bytes_dropped_oversize: u64,
max_buffer: Option<usize>,
overflow_policy: OverflowPolicy,
stop: Option<ReassemblyStop>,
high_watermark: u64,
high_watermark_threshold_pct: Option<u8>,
above_threshold: bool,
high_watermark_crossings: u64,
retransmits: u64,
}
impl BufferedReassembler {
pub fn new() -> Self {
Self::default()
}
pub fn with_max_buffer(mut self, max_bytes: usize) -> Self {
self.max_buffer = Some(max_bytes);
self
}
pub fn with_overflow_policy(mut self, policy: OverflowPolicy) -> Self {
self.overflow_policy = policy;
self
}
pub fn with_high_watermark_threshold(mut self, percent: u8) -> Self {
self.high_watermark_threshold_pct = Some(percent.clamp(1, 100));
self
}
pub fn with_max_ahead(mut self, bytes: u64) -> Self {
self.rules.max_ahead = bytes;
self
}
pub fn with_ack_grace(mut self, grace: std::time::Duration) -> Self {
self.rules.ack_grace = grace;
self
}
pub fn with_reorder_deadline(mut self, deadline: std::time::Duration) -> Self {
self.rules.deadline = deadline;
self
}
pub fn take(&mut self) -> Vec<u8> {
self.above_threshold = false;
self.ready.take_bytes()
}
pub fn dropped_segments(&self) -> u64 {
self.dropped_segments
}
pub fn gaps(&self) -> u64 {
self.gaps
}
pub fn gap_bytes(&self) -> u64 {
self.gap_bytes
}
pub fn bytes_dropped_oversize(&self) -> u64 {
self.bytes_dropped_oversize
}
pub fn buffered_len(&self) -> usize {
self.ready.len()
}
pub fn is_stopped(&self) -> bool {
self.stop.is_some()
}
pub fn high_watermark(&self) -> u64 {
self.high_watermark
}
pub fn retransmits(&self) -> u64 {
self.retransmits
}
pub fn high_watermark_crossings(&self) -> u64 {
self.high_watermark_crossings
}
fn append_with_cap(&mut self, payload: &[u8]) {
if self.stop.is_some() || payload.is_empty() {
return;
}
let Some(cap) = self.max_buffer else {
self.ready.push_data(payload);
self.update_watermark();
return;
};
let projected = self.ready.len() + payload.len();
if projected <= cap {
self.ready.push_data(payload);
self.update_watermark();
return;
}
match self.overflow_policy {
OverflowPolicy::DropFlow => {
self.bytes_dropped_oversize += payload.len() as u64;
self.stop = Some(ReassemblyStop::Overflow);
self.reorder = None;
self.track.drop_stray();
}
OverflowPolicy::SlidingWindow => {
let to_drop = projected - cap;
let buffered = self.ready.len();
if to_drop >= buffered {
self.bytes_dropped_oversize += buffered as u64;
self.ready.drop_front(buffered);
let extra = payload.len().saturating_sub(cap);
self.bytes_dropped_oversize += extra as u64;
self.ready.push_gap(extra as u64);
self.ready.push_data(&payload[extra..]);
} else {
self.bytes_dropped_oversize += to_drop as u64;
self.ready.drop_front(to_drop);
self.ready.push_data(payload);
}
self.update_watermark();
}
}
}
fn note_passthrough(&mut self, len: usize) {
self.high_watermark = self.high_watermark.max(len as u64);
if let (Some(pct), Some(cap)) = (self.high_watermark_threshold_pct, self.max_buffer)
&& len as u64 >= (cap as u64).saturating_mul(pct as u64) / 100
{
self.high_watermark_crossings = self.high_watermark_crossings.saturating_add(1);
}
self.above_threshold = false;
}
#[inline]
fn update_watermark(&mut self) {
let len = self.ready.len() as u64;
if len > self.high_watermark {
self.high_watermark = len;
}
if let (Some(pct), Some(cap)) = (self.high_watermark_threshold_pct, self.max_buffer) {
let trigger = (cap as u64).saturating_mul(pct as u64) / 100;
if len >= trigger {
if !self.above_threshold {
self.above_threshold = true;
self.high_watermark_crossings = self.high_watermark_crossings.saturating_add(1);
}
} else {
self.above_threshold = false;
}
}
}
fn skip_to(&mut self, to: u64, ts: Timestamp) {
let from = self.track.next_off;
if to <= from {
return;
}
let missing = to - from;
self.gaps += 1;
self.gap_bytes += missing;
self.track.record_skip(from, to);
self.ready.push_gap(missing);
self.track.advance_to(to, ts);
}
fn deliver_at(&mut self, off: u64, data: &[u8], ts: Timestamp) {
let next = self.track.next_off;
let end = off + data.len() as u64;
if end <= next {
return;
}
let skip = (next.saturating_sub(off)) as usize;
self.track.advance_to(end, ts);
self.append_with_cap(&data[skip..]);
}
fn release_reorder(&mut self, ts: Timestamp) {
if let Some(c) = self.reorder.take() {
self.skip_to(c.off, ts);
self.deliver_at(c.off, &c.data, ts);
}
}
fn try_reorder(&mut self, ts: Timestamp) {
if self
.reorder
.as_ref()
.is_some_and(|c| c.off <= self.track.next_off)
{
let c = self.reorder.take().expect("checked");
self.deliver_at(c.off, &c.data, ts);
}
}
fn resync(&mut self, c: &Candidate, ts: Timestamp) {
self.release_reorder(ts);
self.skip_to(c.off, ts);
self.deliver_at(c.off, &c.data, ts);
}
fn check_timers(&mut self, now: Timestamp) {
if self.stop.is_some() {
return;
}
if let Some(frontier) = self.track.evidence_due(now, self.rules.ack_grace) {
match self.reorder.as_ref().map(|c| c.off) {
Some(off) if off <= frontier => {
self.ack_confirmed_gaps += 1;
self.release_reorder(now);
}
Some(_) => {}
None => {
self.ack_confirmed_gaps += 1;
self.skip_to(frontier, now);
}
}
}
if self
.reorder
.as_ref()
.is_some_and(|c| now.saturating_sub(c.ts) > self.rules.deadline)
{
self.release_reorder(now);
}
self.track.expire_stray(now, self.rules.deadline * 4);
}
fn feed(&mut self, seq: u32, payload: &[u8], ts: Timestamp, pass: bool) -> SegmentOutcome {
if payload.is_empty() || self.stop.is_some() {
return SegmentOutcome::Buffered;
}
self.track.anchor(seq, ts);
let start = self.track.offset_of(seq);
let end = start + payload.len() as i64;
let next = self.track.next_off as i64;
let max_ahead = self.rules.max_ahead as i64;
if start - next > max_ahead {
if let FarOutcome::Resync(c) =
self.track
.far_ahead(start as u64, payload, ts, self.rules.max_ahead)
{
self.resync(&c, ts);
return self.feed(seq, payload, ts, false);
}
return SegmentOutcome::Buffered;
}
if end <= next {
if end < next - max_ahead {
self.track.out_of_window += 1;
} else if start >= 0 && self.track.in_skipped(start as u64, end as u64) {
self.dropped_segments += 1;
} else {
self.retransmits += 1;
self.on_duplicate(seq, payload, ts);
}
self.check_timers(ts);
return SegmentOutcome::Buffered;
}
let skip = if start < next {
self.retransmits += 1;
self.on_duplicate(seq, payload, ts);
(next - start) as usize
} else {
0
};
if start > next {
if self.reorder.is_some() {
self.release_reorder(ts);
return self.feed(seq, payload, ts, false);
}
self.reorder = Some(Box::new(Candidate {
off: start as u64,
data: payload.to_vec(),
ts,
}));
self.track.last_progress = ts;
self.check_timers(ts);
return SegmentOutcome::Buffered;
}
let len = payload.len() - skip;
if pass
&& self.ready.is_empty()
&& self.reorder.is_none()
&& self.max_buffer.is_none_or(|cap| len <= cap)
{
self.track.advance_to(end as u64, ts);
self.note_passthrough(len);
return SegmentOutcome::Passthrough { skip };
}
self.track.advance_to(end as u64, ts);
self.append_with_cap(&payload[skip..]);
self.try_reorder(ts);
self.check_timers(ts);
SegmentOutcome::Buffered
}
}
impl Reassembler for BufferedReassembler {
fn segment(&mut self, seq: u32, payload: &[u8], ts: Timestamp) {
self.feed(seq, payload, ts, false);
}
fn segment_into(&mut self, seq: u32, payload: &[u8], ts: Timestamp) -> SegmentOutcome {
self.feed(seq, payload, ts, true)
}
fn set_origin(&mut self, seq: u32) {
self.track.anchor(seq, Timestamp::default());
}
fn peer_ack(&mut self, ack: u32, ts: Timestamp) {
if self.stop.is_some() {
return;
}
if let Some(c) = self
.track
.evidence(ack.wrapping_sub(1), ts, self.rules.max_ahead)
{
self.resync(&c, ts);
}
self.check_timers(ts);
}
fn fin_seen(&mut self, end: u32, ts: Timestamp) {
if self.stop.is_some() {
return;
}
if let Some(c) = self.track.fin(end, ts, self.rules.max_ahead) {
self.resync(&c, ts);
}
}
fn drain_into(&mut self, out: &mut StreamChunks) {
self.above_threshold = false;
out.append(&mut self.ready);
if let Some(stop) = self.stop {
out.set_stop(stop);
}
}
fn flush_pending(&mut self) {
if self.stop.is_some() {
return;
}
let ts = self.track.last_progress;
self.release_reorder(ts);
if let Some(frontier) = self.track.evidence_frontier() {
self.ack_confirmed_gaps += 1;
self.skip_to(frontier, ts);
}
self.track.drop_stray();
}
fn advance_time(&mut self, now: Timestamp) {
self.check_timers(now);
}
fn dropped_segments(&self) -> u64 {
self.dropped_segments
}
fn gaps(&self) -> u64 {
self.gaps
}
fn gap_bytes(&self) -> u64 {
self.gap_bytes
}
fn bytes_dropped_oversize(&self) -> u64 {
self.bytes_dropped_oversize
}
fn stop_reason(&self) -> Option<ReassemblyStop> {
self.stop
}
fn high_watermark(&self) -> u64 {
self.high_watermark
}
fn bytes_in_flight(&self) -> u64 {
self.ready.len() as u64
}
fn high_watermark_crossings(&self) -> u64 {
self.high_watermark_crossings
}
fn high_watermark_threshold(&self) -> Option<(u64, u8)> {
match (self.max_buffer, self.high_watermark_threshold_pct) {
(Some(cap), Some(pct)) => Some((cap as u64, pct)),
_ => None,
}
}
fn retransmits(&self) -> u64 {
self.retransmits
}
fn release(&mut self) {
self.ready.clear();
self.ready.release();
self.reorder = None;
self.track.drop_stray();
self.stop.get_or_insert(ReassemblyStop::Memcap);
}
fn current_bytes(&self) -> u64 {
let held = self
.reorder
.as_ref()
.map_or(0, |c| std::mem::size_of::<Candidate>() + c.data.capacity());
(self.ready.len() + held + self.track.heap_bytes()) as u64
}
fn out_of_window_segments(&self) -> u64 {
self.track.out_of_window
}
fn ack_confirmed_gaps(&self) -> u64 {
self.ack_confirmed_gaps
}
fn origin_resets(&self) -> u64 {
self.track.origin_resets
}
}
#[derive(Debug, Default, Clone)]
pub struct BufferedReassemblerFactory {
max_buffer: Option<usize>,
overflow_policy: OverflowPolicy,
high_watermark_threshold_pct: Option<u8>,
pinned: Pinned,
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct Pinned {
pub(crate) max_buffer: bool,
pub(crate) overflow_policy: bool,
pub(crate) high_watermark: bool,
}
impl BufferedReassemblerFactory {
pub fn from_config(config: &FlowTrackerConfig) -> Self {
let mut f = Self::default();
<Self as ReassemblerFactory<()>>::apply_config(&mut f, config);
f
}
pub fn with_max_buffer(mut self, max_bytes: usize) -> Self {
self.max_buffer = Some(max_bytes);
self.pinned.max_buffer = true;
self
}
pub fn unbounded(mut self) -> Self {
self.max_buffer = None;
self.pinned.max_buffer = true;
self
}
pub fn with_overflow_policy(mut self, policy: OverflowPolicy) -> Self {
self.overflow_policy = policy;
self.pinned.overflow_policy = true;
self
}
pub fn with_high_watermark_threshold(mut self, percent: u8) -> Self {
self.high_watermark_threshold_pct = Some(percent.clamp(1, 100));
self.pinned.high_watermark = true;
self
}
}
impl<K: Send + 'static> ReassemblerFactory<K> for BufferedReassemblerFactory {
type Reassembler = BufferedReassembler;
fn new_reassembler(&mut self, _key: &K, _side: FlowSide) -> BufferedReassembler {
let mut r = BufferedReassembler::new();
if let Some(cap) = self.max_buffer {
r = r
.with_max_buffer(cap)
.with_overflow_policy(self.overflow_policy);
}
if let Some(pct) = self.high_watermark_threshold_pct {
r = r.with_high_watermark_threshold(pct);
}
r
}
fn apply_config(&mut self, config: &FlowTrackerConfig) {
if !self.pinned.max_buffer {
self.max_buffer = config.max_reassembler_buffer;
}
if !self.pinned.overflow_policy {
self.overflow_policy = config.overflow_policy;
}
if !self.pinned.high_watermark {
self.high_watermark_threshold_pct = config.reassembler_high_watermark_pct;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn t() -> Timestamp {
Timestamp::default()
}
fn drain(r: &mut impl Reassembler) -> StreamChunks {
let mut out = StreamChunks::new();
r.drain_into(&mut out);
out
}
#[test]
fn chunks_iterate_in_stream_order() {
let mut s = StreamChunks::new();
s.push_gap(3);
s.push_data(b"ab");
s.push_gap(2);
s.push_gap(5); s.push_data(b"c");
let v: Vec<_> = s.iter().collect();
assert_eq!(
v,
vec![
Chunk::Gap(3),
Chunk::Data(b"ab"),
Chunk::Gap(7),
Chunk::Data(b"c")
]
);
assert_eq!(s.gap_bytes(), 10);
assert_eq!(s.data(), b"abc");
}
#[test]
fn chunks_trailing_gap_is_yielded() {
let mut s = StreamChunks::new();
s.push_data(b"ab");
s.push_gap(4);
let v: Vec<_> = s.iter().collect();
assert_eq!(v, vec![Chunk::Data(b"ab"), Chunk::Gap(4)]);
}
#[test]
fn chunks_append_rebases_gaps_and_keeps_stop() {
let mut a = StreamChunks::new();
a.push_data(b"xy");
let mut b = StreamChunks::new();
b.push_gap(1);
b.push_data(b"z");
b.set_stop(ReassemblyStop::Overflow);
a.append(&mut b);
assert!(b.is_empty());
let v: Vec<_> = a.iter().collect();
assert_eq!(
v,
vec![Chunk::Data(b"xy"), Chunk::Gap(1), Chunk::Data(b"z")]
);
assert_eq!(a.stop(), Some(ReassemblyStop::Overflow));
}
#[test]
fn drop_front_turns_bytes_into_a_leading_gap() {
let mut s = StreamChunks::new();
s.push_data(b"abc");
s.push_gap(10);
s.push_data(b"def");
s.drop_front(4); let v: Vec<_> = s.iter().collect();
assert_eq!(v, vec![Chunk::Gap(14), Chunk::Data(b"ef")]);
}
#[test]
fn in_order_concatenates() {
let mut r = BufferedReassembler::new();
r.segment(100, b"abc", t());
r.segment(103, b"def", t());
r.segment(106, b"gh", t());
assert_eq!(r.take(), b"abcdefgh");
assert_eq!(r.gaps(), 0);
}
#[test]
fn hole_is_skipped_and_reported_not_wedged() {
let mut r = BufferedReassembler::new();
r.segment(100, b"hello", t()); r.segment(110, b"world", t()); r.segment(115, b"!!", t()); let out = drain(&mut r);
let v: Vec<_> = out.iter().collect();
assert_eq!(
v,
vec![
Chunk::Data(b"hello"),
Chunk::Gap(5),
Chunk::Data(b"world!!")
]
);
assert_eq!(r.gaps(), 1);
assert_eq!(r.gap_bytes(), 5);
}
#[test]
fn a_single_reordering_is_repaired() {
let mut r = BufferedReassembler::new();
r.segment(0, b"hello", t());
r.segment(10, b"world", t()); r.segment(5, b"MIDDL", t());
assert_eq!(r.gaps(), 0);
assert_eq!(r.take(), b"helloMIDDLworld");
}
#[test]
fn late_segment_inside_a_skipped_hole_is_dropped_not_a_retransmit() {
let mut r = BufferedReassembler::new();
r.segment(0, b"hello", t());
r.segment(10, b"world", t()); r.segment(20, b"again", t()); r.segment(5, b"MIDDL", t()); assert_eq!(r.dropped_segments(), 1);
assert_eq!(r.retransmits(), 0);
assert_eq!(r.take(), b"helloworld");
}
#[test]
fn stray_beyond_the_window_is_dropped() {
let mut r = BufferedReassembler::new().with_max_ahead(1000);
r.segment(0, b"abc", t());
r.segment(5_000_000, b"STRAY", t());
r.segment(3, b"def", t());
assert_eq!(r.take(), b"abcdef");
assert_eq!(r.out_of_window_segments(), 1);
assert_eq!(r.gaps(), 0);
}
#[test]
fn syn_origin_repairs_reordered_first_segments() {
let mut r = BufferedReassembler::new();
r.set_origin(100);
r.segment(105, b"world", t());
r.segment(100, b"hello", t());
assert_eq!(r.take(), b"helloworld");
assert_eq!(r.retransmits(), 0);
}
#[test]
fn acked_hole_is_skipped_after_the_grace() {
let mut r = BufferedReassembler::new();
r.segment(0, b"abc", Timestamp::new(1, 0));
r.segment(6, b"ghi", Timestamp::new(1, 0));
r.peer_ack(9, Timestamp::new(1, 0));
assert_eq!(r.gaps(), 0);
r.advance_time(Timestamp::new(2, 0));
assert_eq!(r.ack_confirmed_gaps(), 1);
assert_eq!(r.take(), b"abcghi");
}
#[test]
fn take_resets_buffer_only() {
let mut r = BufferedReassembler::new();
r.segment(0, b"abc", t());
assert_eq!(r.take(), b"abc");
assert_eq!(r.buffered_len(), 0);
r.segment(3, b"def", t());
assert_eq!(r.take(), b"def");
}
#[test]
fn empty_payload_ignored() {
let mut r = BufferedReassembler::new();
r.segment(0, b"", t());
assert!(!r.track.anchored());
}
#[test]
fn factory_creates_fresh_reassembler() {
let mut f = BufferedReassemblerFactory::default();
let mut r1: BufferedReassembler = f.new_reassembler(&42u32, FlowSide::Initiator);
let mut r2: BufferedReassembler = f.new_reassembler(&42u32, FlowSide::Responder);
r1.segment(0, b"x", t());
r2.segment(0, b"y", t());
assert_eq!(r1.take(), b"x");
assert_eq!(r2.take(), b"y");
}
#[test]
fn cap_unbounded_by_default() {
let mut r = BufferedReassembler::new();
r.segment(0, &[0u8; 10_000], t());
assert_eq!(r.buffered_len(), 10_000);
assert_eq!(r.bytes_dropped_oversize(), 0);
assert!(!r.is_stopped());
}
#[test]
fn sliding_window_drop_is_reported_as_a_leading_gap() {
let mut r = BufferedReassembler::new().with_max_buffer(100);
r.segment(0, &[b'a'; 80], t());
r.segment(80, &[b'b'; 80], t()); assert_eq!(r.buffered_len(), 100);
assert_eq!(r.bytes_dropped_oversize(), 60);
let out = drain(&mut r);
let v: Vec<_> = out.iter().collect();
assert_eq!(v[0], Chunk::Gap(60));
assert_eq!(out.data()[..20], [b'a'; 20]);
assert_eq!(out.data()[20..], [b'b'; 80]);
assert_eq!(r.gaps(), 0);
}
#[test]
fn cap_payload_bigger_than_cap_keeps_tail() {
let mut r = BufferedReassembler::new().with_max_buffer(50);
let payload: Vec<u8> = (0u8..100).collect();
r.segment(0, &payload, t());
assert_eq!(r.buffered_len(), 50);
assert_eq!(r.bytes_dropped_oversize(), 50);
let out = drain(&mut r);
assert_eq!(out.gap_bytes(), 50);
assert_eq!(out.data(), (50u8..100).collect::<Vec<u8>>());
}
#[test]
fn drop_flow_stops_but_keeps_what_was_already_in_order() {
let mut r = BufferedReassembler::new()
.with_max_buffer(100)
.with_overflow_policy(OverflowPolicy::DropFlow);
r.segment(0, &[b'a'; 80], t());
assert!(!r.is_stopped());
r.segment(80, &[b'b'; 80], t()); assert!(r.is_stopped());
assert_eq!(r.stop_reason(), Some(ReassemblyStop::Overflow));
assert_eq!(r.bytes_dropped_oversize(), 80);
r.segment(160, &[b'c'; 10], t()); let out = drain(&mut r);
assert_eq!(out.data(), &[b'a'; 80][..]);
assert_eq!(out.stop(), Some(ReassemblyStop::Overflow));
assert_eq!(drain(&mut r).stop(), Some(ReassemblyStop::Overflow));
}
#[test]
fn cap_drop_flow_does_not_poison_under_cap() {
let mut r = BufferedReassembler::new()
.with_max_buffer(100)
.with_overflow_policy(OverflowPolicy::DropFlow);
r.segment(0, &[b'a'; 50], t());
r.segment(50, &[b'b'; 50], t());
assert!(!r.is_stopped());
assert_eq!(r.buffered_len(), 100);
}
#[test]
fn factory_propagates_cap_and_policy() {
let mut f = BufferedReassemblerFactory::default()
.with_max_buffer(64)
.with_overflow_policy(OverflowPolicy::DropFlow);
let mut r: BufferedReassembler = f.new_reassembler(&0u32, FlowSide::Initiator);
r.segment(0, &[0u8; 100], t());
assert!(r.is_stopped());
}
#[test]
fn factory_from_config_uses_the_tracker_config() {
let cfg = FlowTrackerConfig {
max_reassembler_buffer: Some(10),
overflow_policy: OverflowPolicy::DropFlow,
reassembler_high_watermark_pct: Some(50),
..Default::default()
};
let mut f = BufferedReassemblerFactory::from_config(&cfg);
let mut r: BufferedReassembler = f.new_reassembler(&0u32, FlowSide::Initiator);
assert_eq!(r.high_watermark_threshold(), Some((10, 50)));
r.segment(0, &[0u8; 11], t());
assert!(r.is_stopped());
}
#[test]
fn explicit_factory_settings_win_over_the_config() {
let cfg = FlowTrackerConfig {
max_reassembler_buffer: Some(1_000),
overflow_policy: OverflowPolicy::DropFlow,
..Default::default()
};
let mut f = BufferedReassemblerFactory::default().with_max_buffer(10);
<BufferedReassemblerFactory as ReassemblerFactory<u32>>::apply_config(&mut f, &cfg);
let mut r: BufferedReassembler = f.new_reassembler(&0u32, FlowSide::Initiator);
r.segment(0, &[0u8; 11], t());
assert!(r.is_stopped());
let mut f = BufferedReassemblerFactory::default().unbounded();
<BufferedReassemblerFactory as ReassemblerFactory<u32>>::apply_config(&mut f, &cfg);
let mut r: BufferedReassembler = f.new_reassembler(&0u32, FlowSide::Initiator);
r.segment(0, &[0u8; 5_000], t());
assert_eq!(r.buffered_len(), 5_000);
}
#[test]
fn release_stops_with_memcap_reason() {
let mut r = BufferedReassembler::new();
r.segment(0, b"abc", t());
r.release();
assert_eq!(r.current_bytes(), 0);
assert_eq!(r.stop_reason(), Some(ReassemblyStop::Memcap));
r.segment(3, b"def", t());
assert_eq!(r.current_bytes(), 0);
}
#[test]
fn high_watermark_tracks_peak_buffer_unbounded() {
let mut r = BufferedReassembler::new();
r.segment(0, &[b'a'; 50], t());
assert_eq!(r.high_watermark(), 50);
let _ = r.take();
assert_eq!(r.high_watermark(), 50);
r.segment(50, &[b'b'; 20], t());
assert_eq!(r.high_watermark(), 50);
let _ = r.take();
r.segment(70, &[b'c'; 100], t());
assert_eq!(r.high_watermark(), 100);
}
#[test]
fn high_watermark_threshold_crosses_once() {
let mut r = BufferedReassembler::new()
.with_max_buffer(100)
.with_high_watermark_threshold(80);
r.segment(0, &[b'a'; 50], t());
assert_eq!(r.high_watermark_crossings(), 0);
r.segment(50, &[b'b'; 40], t());
assert_eq!(r.high_watermark_crossings(), 1);
r.segment(90, &[b'c'; 5], t());
assert_eq!(r.high_watermark_crossings(), 1);
let _ = drain(&mut r);
r.segment(95, &[b'd'; 85], t());
assert_eq!(r.high_watermark_crossings(), 2);
}
#[test]
fn high_watermark_threshold_info_visible_via_trait() {
let r = BufferedReassembler::new()
.with_max_buffer(200)
.with_high_watermark_threshold(75);
assert_eq!(r.high_watermark_threshold(), Some((200, 75)));
let r2 = BufferedReassembler::new().with_high_watermark_threshold(75);
assert_eq!(r2.high_watermark_threshold(), None);
let r3 = BufferedReassembler::new()
.with_max_buffer(100)
.with_high_watermark_threshold(0);
assert_eq!(r3.high_watermark_threshold(), Some((100, 1)));
}
#[test]
fn exact_retransmit_classified_as_retransmit() {
let mut r = BufferedReassembler::new();
r.segment(0, b"hello", Timestamp::new(1, 0));
r.segment(0, b"hello", Timestamp::new(2, 0));
assert_eq!(r.retransmits(), 1);
assert_eq!(r.dropped_segments(), 0);
assert_eq!(r.take(), b"hello");
}
#[test]
fn partial_overlap_delivers_the_new_tail() {
let mut r = BufferedReassembler::new();
r.segment(0, b"hello", t()); r.segment(3, b"lo world", t()); assert_eq!(r.retransmits(), 1);
assert_eq!(r.take(), b"hello world");
}
#[test]
fn sequence_wraparound_is_seamless() {
let mut r = BufferedReassembler::new();
r.segment(u32::MAX - 2, b"abc", t());
r.segment(0, b"def", t());
assert_eq!(r.gaps(), 0);
assert_eq!(r.take(), b"abcdef");
}
#[test]
fn on_duplicate_receives_ts() {
use std::sync::{Arc, Mutex};
#[derive(Default)]
struct Spy {
seen: Arc<Mutex<Vec<Timestamp>>>,
}
impl Reassembler for Spy {
fn segment(&mut self, _seq: u32, _payload: &[u8], _ts: Timestamp) {}
fn on_duplicate(&mut self, _seq: u32, _payload: &[u8], ts: Timestamp) {
self.seen.lock().unwrap().push(ts);
}
}
let seen = Arc::new(Mutex::new(Vec::new()));
let mut spy = Spy { seen: seen.clone() };
spy.on_duplicate(0, b"hello", Timestamp::new(7, 42));
assert_eq!(seen.lock().unwrap().as_slice(), &[Timestamp::new(7, 42)]);
}
#[test]
fn default_stop_reason_is_none() {
struct P;
impl Reassembler for P {
fn segment(&mut self, _: u32, _: &[u8], _: Timestamp) {}
}
assert_eq!(P.stop_reason(), None);
}
}