trillium-http 1.3.3

the http implementation for the trillium toolkit
Documentation
//! Bounded ledger of recently-closed streams and how they closed.
//!
//! Consulted when a peer frame arrives on a stream id that's no longer in the active map to
//! pick the right error category. See [`ClosedReason`].

use std::collections::{HashMap, VecDeque};

/// Why a stream transitioned to the closed state — dictates the error category for any
/// subsequent frame the peer sends on that stream id:
/// - `Reset`: closed via `RST_STREAM` (either direction). Subsequent frames → stream-level
///   `STREAM_CLOSED`.
/// - `EndStream`: closed via `END_STREAM` on both sides. Subsequent frames (other than
///   `WINDOW_UPDATE` / `PRIORITY` / `RST_STREAM`) → connection-level `STREAM_CLOSED`.
///
/// Streams that were never opened and are merely implicitly closed by a higher-id HEADERS
/// don't appear in the ledger; the fall-through case there is connection-level
/// `PROTOCOL_ERROR`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::h2) enum ClosedReason {
    Reset,
    EndStream,
}

/// Bounded FIFO of recently-closed streams and how they closed.
///
/// Fixed cap — this is a correctness mechanism, not a concurrency-scaled structure. A
/// well-behaved peer never sends frames on a stream it knows is closed; the ledger only
/// needs to span a handful of RTTs between our close and a misbehaving peer's stale
/// frame. Oldest entries evict on overflow; evicted lookups fall through to the
/// connection-level `PROTOCOL_ERROR` default.
#[derive(Debug, Default)]
pub(super) struct ClosedStreams {
    map: HashMap<u32, ClosedReason>,
    fifo: VecDeque<u32>,
}

impl ClosedStreams {
    const CAP: usize = 128;

    /// Record (or update) the close reason for `stream_id`. Idempotent on repeated calls
    /// for the same id; the most recent reason wins. A stream can be recorded as
    /// `EndStream` after already being recorded as `Reset`, which is benign — Reset is
    /// authoritative since the Ok path doesn't fire when the error path did.
    pub(super) fn record(&mut self, stream_id: u32, reason: ClosedReason) {
        if self.map.insert(stream_id, reason).is_none() {
            self.fifo.push_back(stream_id);
            while self.fifo.len() > Self::CAP {
                if let Some(old) = self.fifo.pop_front() {
                    self.map.remove(&old);
                }
            }
        }
    }

    pub(super) fn reason(&self, stream_id: u32) -> Option<ClosedReason> {
        self.map.get(&stream_id).copied()
    }
}