ipfrs_network/stream_multiplexer.rs
1//! Stream Multiplexer — multiplexes multiple logical streams over a single connection
2//! with flow control, priority scheduling, and proper frame lifecycle management.
3//!
4//! ## Design
5//!
6//! Each logical [`StreamId`] carries its own sequence counter, send/receive windows,
7//! and priority. The shared send queue is a max-[`BinaryHeap`] keyed on
8//! `(priority_weight, Reverse(enqueue_sequence))` so that within the same priority
9//! tier frames are emitted in FIFO order.
10//!
11//! ## Frame flags (FrameFlags bit positions)
12//!
13//! | Bit | Name | Value | Meaning |
14//! |------|------|-------|-------------------------------|
15//! | 0 | SYN | 0x01 | Open (create) a new stream |
16//! | 1 | FIN | 0x02 | Close stream after this frame |
17//! | 2 | RST | 0x04 | Reset stream immediately |
18//! | 3 | ACK | 0x08 | Acknowledgement |
19//! | 4 | DATA | 0x10 | Frame carries payload data |
20//!
21//! ## Legacy flag constants (raw u8)
22//!
23//! For backwards compatibility the original raw constants are preserved:
24//! `FLAG_FIN=0x01`, `FLAG_RST=0x02`, `FLAG_SYN=0x04`.
25//!
26//! ## Flow control
27//!
28//! `send_window` tracks how many bytes may still be enqueued for sending on a
29//! given stream. Each call to [`StreamMultiplexer::send`] checks that
30//! `data.len() <= send_window` and deducts the amount from the window.
31//! [`StreamMultiplexer::update_window`] adds credits back (simulating receipt of
32//! a window-update ACK from the remote side).
33
34use std::cmp::Reverse;
35use std::collections::{BinaryHeap, HashMap, VecDeque};
36use std::fmt;
37
38// ─────────────────────────────────────────────────────────────────────────────
39// Legacy raw flag constants (backward-compatible; raw u8)
40// ─────────────────────────────────────────────────────────────────────────────
41
42/// Bit mask for the FIN flag in [`StreamFrame::flags`] (legacy raw u8 form).
43pub const FLAG_FIN: u8 = 0b0000_0001;
44/// Bit mask for the RST flag in [`StreamFrame::flags`] (legacy raw u8 form).
45pub const FLAG_RST: u8 = 0b0000_0010;
46/// Bit mask for the SYN flag in [`StreamFrame::flags`] (legacy raw u8 form).
47pub const FLAG_SYN: u8 = 0b0000_0100;
48
49// ─────────────────────────────────────────────────────────────────────────────
50// FrameFlags — typed bitfield
51// ─────────────────────────────────────────────────────────────────────────────
52
53/// Bitfield wrapper for stream-frame control flags.
54///
55/// # Flag layout
56///
57/// | Bit | Constant | Meaning |
58/// |-----|-----------------------|-------------------------------|
59/// | 0 | [`FrameFlags::SYN`] | Open (create) a new stream |
60/// | 1 | [`FrameFlags::FIN`] | Close stream gracefully |
61/// | 2 | [`FrameFlags::RST`] | Reset stream immediately |
62/// | 3 | [`FrameFlags::ACK`] | Acknowledgement frame |
63/// | 4 | [`FrameFlags::DATA`] | Frame carries payload data |
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
65pub struct FrameFlags(pub u8);
66
67impl FrameFlags {
68 /// SYN — open a new stream (bit 0).
69 pub const SYN: u8 = 0x01;
70 /// FIN — close stream gracefully (bit 1).
71 pub const FIN: u8 = 0x02;
72 /// RST — reset stream immediately (bit 2).
73 pub const RST: u8 = 0x04;
74 /// ACK — acknowledgement (bit 3).
75 pub const ACK: u8 = 0x08;
76 /// DATA — frame carries payload (bit 4).
77 pub const DATA: u8 = 0x10;
78
79 /// Create `FrameFlags` from a raw byte.
80 #[inline]
81 pub fn new(raw: u8) -> Self {
82 Self(raw)
83 }
84
85 /// Returns `true` if the SYN bit is set.
86 #[inline]
87 pub fn is_syn(self) -> bool {
88 self.0 & Self::SYN != 0
89 }
90
91 /// Returns `true` if the FIN bit is set.
92 #[inline]
93 pub fn is_fin(self) -> bool {
94 self.0 & Self::FIN != 0
95 }
96
97 /// Returns `true` if the RST bit is set.
98 #[inline]
99 pub fn is_rst(self) -> bool {
100 self.0 & Self::RST != 0
101 }
102
103 /// Returns `true` if the ACK bit is set.
104 #[inline]
105 pub fn is_ack(self) -> bool {
106 self.0 & Self::ACK != 0
107 }
108
109 /// Returns `true` if the DATA bit is set.
110 #[inline]
111 pub fn is_data(self) -> bool {
112 self.0 & Self::DATA != 0
113 }
114
115 /// Set a flag bit and return the updated value.
116 #[inline]
117 pub fn with(self, flag: u8) -> Self {
118 Self(self.0 | flag)
119 }
120}
121
122impl fmt::Display for FrameFlags {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 let mut parts: Vec<&str> = Vec::new();
125 if self.is_syn() {
126 parts.push("SYN");
127 }
128 if self.is_fin() {
129 parts.push("FIN");
130 }
131 if self.is_rst() {
132 parts.push("RST");
133 }
134 if self.is_ack() {
135 parts.push("ACK");
136 }
137 if self.is_data() {
138 parts.push("DATA");
139 }
140 if parts.is_empty() {
141 write!(f, "NONE")
142 } else {
143 write!(f, "{}", parts.join("|"))
144 }
145 }
146}
147
148// ─────────────────────────────────────────────────────────────────────────────
149// StreamId
150// ─────────────────────────────────────────────────────────────────────────────
151
152/// Newtype identifier for a logical stream.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
154pub struct StreamId(pub u32);
155
156impl fmt::Display for StreamId {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 write!(f, "stream:{}", self.0)
159 }
160}
161
162// ─────────────────────────────────────────────────────────────────────────────
163// StreamPriority
164// ─────────────────────────────────────────────────────────────────────────────
165
166/// Priority level for a logical stream.
167///
168/// Higher priority streams are dequeued first by the multiplexer. Within the
169/// same priority, frames are emitted in FIFO order.
170#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
171pub enum StreamPriority {
172 /// Best-effort delivery — weight 0.
173 Background = 0,
174 /// Low priority — weight 1.
175 Low = 1,
176 /// Normal priority — weight 2.
177 Normal = 2,
178 /// High priority — weight 4.
179 High = 4,
180 /// Critical (real-time) — weight 8.
181 Critical = 8,
182}
183
184impl StreamPriority {
185 /// Numeric weight used for priority scheduling.
186 ///
187 /// `Critical` → 8, `High` → 4, `Normal` → 2, `Low` → 1, `Background` → 0.
188 #[inline]
189 pub fn weight(self) -> u32 {
190 match self {
191 Self::Critical => 8,
192 Self::High => 4,
193 Self::Normal => 2,
194 Self::Low => 1,
195 Self::Background => 0,
196 }
197 }
198}
199
200/// Convert a raw priority byte (0-255) to the nearest [`StreamPriority`] tier.
201pub fn priority_from_u8(p: u8) -> StreamPriority {
202 match p {
203 0 => StreamPriority::Background,
204 1..=63 => StreamPriority::Low,
205 64..=127 => StreamPriority::Normal,
206 128..=191 => StreamPriority::High,
207 192..=255 => StreamPriority::Critical,
208 }
209}
210
211// ─────────────────────────────────────────────────────────────────────────────
212// StreamState
213// ─────────────────────────────────────────────────────────────────────────────
214
215/// Lifecycle state of a logical stream.
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum StreamState {
218 /// SYN has been sent but not yet acknowledged; stream is being established.
219 Opening,
220 /// The stream is fully open and can send/receive data.
221 Open,
222 /// A FIN has been sent or received; the stream is draining.
223 HalfClosed,
224 /// The stream has been cleanly closed (both sides have finished).
225 Closed,
226 /// The stream was abruptly reset (RST sent or received).
227 Reset,
228}
229
230// ─────────────────────────────────────────────────────────────────────────────
231// StreamFrame
232// ─────────────────────────────────────────────────────────────────────────────
233
234/// Fixed header length in bytes for [`StreamFrame`] wire encoding.
235///
236/// Layout: `stream_id(4) + sequence_num(8) + payload_len(4) + flags(1) + timestamp(8) = 25`.
237const FRAME_HEADER_LEN: usize = 25;
238
239/// A framed unit of data for one logical stream.
240///
241/// # Wire format (little-endian)
242///
243/// ```text
244/// [stream_id: u32 LE][sequence_num: u64 LE][payload_len: u32 LE][flags: u8][timestamp: u64 LE][payload...]
245/// ```
246#[derive(Debug, Clone)]
247pub struct StreamFrame {
248 /// Which stream this frame belongs to.
249 pub stream_id: StreamId,
250 /// Monotonically increasing per-stream sequence number.
251 pub sequence: u64,
252 /// Payload bytes.
253 pub data: Vec<u8>,
254 /// Control flags: [`FLAG_FIN`], [`FLAG_RST`], [`FLAG_SYN`].
255 pub flags: u8,
256 /// Wall-clock timestamp at the time the frame was created (caller-supplied).
257 pub timestamp: u64,
258}
259
260impl StreamFrame {
261 // ── Legacy flag helpers (raw u8 constants) ─────────────────────────────
262
263 /// Returns `true` if the FIN flag is set (legacy raw-u8 form).
264 #[inline]
265 pub fn is_fin(&self) -> bool {
266 self.flags & FLAG_FIN != 0
267 }
268
269 /// Returns `true` if the RST flag is set (legacy raw-u8 form).
270 #[inline]
271 pub fn is_rst(&self) -> bool {
272 self.flags & FLAG_RST != 0
273 }
274
275 /// Returns `true` if the SYN flag is set (legacy raw-u8 form).
276 #[inline]
277 pub fn is_syn(&self) -> bool {
278 self.flags & FLAG_SYN != 0
279 }
280
281 /// Returns `true` if this is a pure control frame (no payload bytes matter).
282 #[inline]
283 pub fn is_control(&self) -> bool {
284 self.flags != 0
285 }
286
287 // ── Wire encoding / decoding ───────────────────────────────────────────
288
289 /// Encode the frame into a `Vec<u8>` using the fixed wire format.
290 ///
291 /// Wire layout (all little-endian):
292 /// `stream_id(4) + sequence_num(8) + payload_len(4) + flags(1) + timestamp(8) + payload`
293 ///
294 /// Total header: 25 bytes.
295 pub fn encode(&self) -> Vec<u8> {
296 let payload_len = self.data.len();
297 let mut buf = Vec::with_capacity(FRAME_HEADER_LEN + payload_len);
298 buf.extend_from_slice(&self.stream_id.0.to_le_bytes());
299 buf.extend_from_slice(&self.sequence.to_le_bytes());
300 buf.extend_from_slice(&(payload_len as u32).to_le_bytes());
301 buf.push(self.flags);
302 buf.extend_from_slice(&self.timestamp.to_le_bytes());
303 buf.extend_from_slice(&self.data);
304 buf
305 }
306
307 /// Decode a frame from a byte slice produced by [`StreamFrame::encode`].
308 ///
309 /// # Errors
310 ///
311 /// Returns [`MuxError::FrameTooLarge`] if `data` is shorter than the
312 /// minimum header size or shorter than the declared payload length.
313 pub fn decode(data: &[u8]) -> Result<Self, MuxError> {
314 if data.len() < FRAME_HEADER_LEN {
315 return Err(MuxError::FrameTooLarge(data.len()));
316 }
317
318 let stream_id = u32::from_le_bytes(
319 data[0..4]
320 .try_into()
321 .map_err(|_| MuxError::FrameTooLarge(data.len()))?,
322 );
323 let sequence = u64::from_le_bytes(
324 data[4..12]
325 .try_into()
326 .map_err(|_| MuxError::FrameTooLarge(data.len()))?,
327 );
328 let payload_len = u32::from_le_bytes(
329 data[12..16]
330 .try_into()
331 .map_err(|_| MuxError::FrameTooLarge(data.len()))?,
332 ) as usize;
333 let flags = data[16];
334 let timestamp = u64::from_le_bytes(
335 data[17..25]
336 .try_into()
337 .map_err(|_| MuxError::FrameTooLarge(data.len()))?,
338 );
339
340 let expected_total = FRAME_HEADER_LEN + payload_len;
341 if data.len() < expected_total {
342 return Err(MuxError::FrameTooLarge(data.len()));
343 }
344
345 let payload = data[FRAME_HEADER_LEN..FRAME_HEADER_LEN + payload_len].to_vec();
346
347 Ok(Self {
348 stream_id: StreamId(stream_id),
349 sequence,
350 data: payload,
351 flags,
352 timestamp,
353 })
354 }
355}
356
357// ─────────────────────────────────────────────────────────────────────────────
358// StreamInfo — read-only statistics snapshot for a single stream
359// ─────────────────────────────────────────────────────────────────────────────
360
361/// Read-only snapshot of per-stream statistics.
362#[derive(Debug, Clone)]
363pub struct StreamInfo {
364 /// Stream identifier.
365 pub id: StreamId,
366 /// Current lifecycle state.
367 pub state: StreamState,
368 /// Total payload bytes enqueued for sending.
369 pub bytes_sent: u64,
370 /// Total payload bytes received.
371 pub bytes_received: u64,
372 /// Total frames enqueued for sending (including control frames).
373 pub frames_sent: u64,
374 /// Total frames received.
375 pub frames_received: u64,
376 /// Timestamp (µs) when the stream was opened.
377 pub opened_at: u64,
378 /// Timestamp (µs) of the last send or receive activity.
379 pub last_activity: u64,
380 /// Raw priority byte supplied at stream creation.
381 pub priority: u8,
382}
383
384// ─────────────────────────────────────────────────────────────────────────────
385// LogicalStream — internal mutable state for a single stream
386// ─────────────────────────────────────────────────────────────────────────────
387
388/// State and bookkeeping for a single logical stream.
389#[derive(Debug, Clone)]
390pub struct LogicalStream {
391 /// Identifier of this stream.
392 pub id: StreamId,
393 /// Priority used for scheduling send frames.
394 pub priority: StreamPriority,
395 /// Current lifecycle state.
396 pub state: StreamState,
397 /// Remaining send window (bytes that may still be enqueued for sending).
398 pub send_window: u32,
399 /// Remaining receive window (bytes we are willing to accept from remote).
400 pub recv_window: u32,
401 /// Next sequence number to use when enqueuing a send frame.
402 pub send_seq: u64,
403 /// Expected sequence number for the next incoming frame.
404 pub recv_seq: u64,
405 /// Cumulative bytes enqueued for sending (payload only).
406 pub bytes_sent: u64,
407 /// Cumulative bytes received (payload only).
408 pub bytes_received: u64,
409 /// Cumulative frames enqueued for sending (including control frames).
410 pub frames_sent: u64,
411 /// Cumulative frames received.
412 pub frames_received: u64,
413 /// Timestamp (µs) when the stream was opened.
414 pub opened_at: u64,
415 /// Timestamp (µs) of the last activity (send or receive).
416 pub last_activity: u64,
417 /// Raw priority byte (0-255) supplied at stream creation.
418 pub priority_raw: u8,
419 /// Bounded receive buffer (payloads in arrival order).
420 recv_buffer: VecDeque<Vec<u8>>,
421 /// Maximum number of payloads buffered on the receive side.
422 recv_buffer_max: usize,
423}
424
425// ─────────────────────────────────────────────────────────────────────────────
426// MultiplexerConfig
427// ─────────────────────────────────────────────────────────────────────────────
428
429/// Configuration for [`StreamMultiplexer`].
430#[derive(Debug, Clone)]
431pub struct MultiplexerConfig {
432 /// Maximum number of concurrently open streams.
433 pub max_streams: usize,
434 /// Initial send/receive window size for each new stream (bytes).
435 pub default_window_size: u32,
436 /// Maximum payload bytes per frame when fragmenting.
437 pub max_frame_size: usize,
438 /// When `false`, all frames are enqueued as equal priority (FIFO).
439 pub enable_priority: bool,
440 /// Maximum number of payload entries held in the per-stream receive buffer.
441 pub recv_buffer_capacity: usize,
442 /// Maximum number of payload entries held in the per-stream send buffer
443 /// (currently informational; actual cap is `send_window`).
444 pub send_buffer_capacity: usize,
445 /// Idle timeout in microseconds. Streams inactive longer than this are
446 /// eligible for expiry via [`StreamMultiplexer::expire_idle`].
447 pub idle_timeout_us: u64,
448}
449
450impl Default for MultiplexerConfig {
451 fn default() -> Self {
452 Self {
453 max_streams: 256,
454 default_window_size: 65_536,
455 max_frame_size: 16_384,
456 enable_priority: true,
457 recv_buffer_capacity: 256,
458 send_buffer_capacity: 256,
459 idle_timeout_us: 30_000_000, // 30 seconds
460 }
461 }
462}
463
464// ─────────────────────────────────────────────────────────────────────────────
465// MultiplexerStats — richer statistics snapshot
466// ─────────────────────────────────────────────────────────────────────────────
467
468/// Rich statistics snapshot for the multiplexer (task-spec version).
469#[derive(Debug, Clone, PartialEq, Eq)]
470pub struct MultiplexerStats {
471 /// Number of streams currently in the [`StreamState::Open`] or
472 /// [`StreamState::Opening`] state.
473 pub active_streams: usize,
474 /// Total streams ever opened (monotonically increasing).
475 pub total_streams_opened: u64,
476 /// Total frames ever dequeued and handed to the caller.
477 pub total_frames_sent: u64,
478 /// Total frames processed via [`StreamMultiplexer::receive`].
479 pub total_frames_received: u64,
480 /// Total payload bytes enqueued for sending across all streams.
481 pub total_bytes_sent: u64,
482 /// Total payload bytes received across all streams.
483 pub total_bytes_received: u64,
484 /// Frames that were dropped due to a full receive buffer.
485 pub dropped_frames: u64,
486}
487
488// ─────────────────────────────────────────────────────────────────────────────
489// PrioritizedFrame (internal heap element)
490// ─────────────────────────────────────────────────────────────────────────────
491
492/// Wrapper that gives [`StreamFrame`] a total ordering for the
493/// [`BinaryHeap`]-based priority send queue.
494///
495/// Ordering rule: higher `priority_weight` first; ties broken by lower
496/// enqueue `sequence` first (FIFO within a priority tier).
497#[derive(Debug)]
498struct PrioritizedFrame {
499 /// Effective weight of the stream's priority at enqueue time.
500 priority_weight: u32,
501 /// Global monotonic enqueue counter used for FIFO within a priority tier.
502 sequence: u64,
503 /// The actual frame payload.
504 frame: StreamFrame,
505}
506
507impl PartialEq for PrioritizedFrame {
508 fn eq(&self, other: &Self) -> bool {
509 self.priority_weight == other.priority_weight && self.sequence == other.sequence
510 }
511}
512
513impl Eq for PrioritizedFrame {}
514
515impl PartialOrd for PrioritizedFrame {
516 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
517 Some(self.cmp(other))
518 }
519}
520
521impl Ord for PrioritizedFrame {
522 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
523 // Primary: higher weight wins (max-heap).
524 self.priority_weight
525 .cmp(&other.priority_weight)
526 // Tie-break: lower enqueue sequence wins (FIFO).
527 .then_with(|| Reverse(self.sequence).cmp(&Reverse(other.sequence)))
528 }
529}
530
531// ─────────────────────────────────────────────────────────────────────────────
532// MuxEvent
533// ─────────────────────────────────────────────────────────────────────────────
534
535/// Events emitted by [`StreamMultiplexer::receive`] and other operations.
536#[derive(Debug, Clone, PartialEq, Eq)]
537pub enum MuxEvent {
538 /// A new stream was opened (SYN received from remote).
539 StreamOpened(StreamId),
540 /// A stream was gracefully closed (FIN received).
541 StreamClosed(StreamId),
542 /// A stream was forcefully reset (RST received).
543 StreamReset(StreamId),
544 /// A data frame was received on the given stream.
545 FrameReceived(StreamId),
546 /// The receive buffer for the stream is full; a frame was dropped.
547 SendBufferFull(StreamId),
548 /// An idle stream was expired by [`StreamMultiplexer::expire_idle`].
549 IdleStreamExpired(StreamId),
550}
551
552// ─────────────────────────────────────────────────────────────────────────────
553// MuxError
554// ─────────────────────────────────────────────────────────────────────────────
555
556/// Errors returned by [`StreamMultiplexer`] operations.
557#[derive(Debug, Clone, PartialEq, Eq)]
558pub enum MuxError {
559 /// All stream slots are occupied.
560 MaxStreamsReached,
561 /// No stream with the given id exists.
562 StreamNotFound(u32),
563 /// The stream exists but is not in [`StreamState::Open`].
564 StreamNotOpen(u32),
565 /// The send window would be exceeded by the requested send.
566 WindowExhausted(u32),
567 /// The incoming frame carries an out-of-order sequence number.
568 SequenceError {
569 /// The sequence number we expected.
570 expected: u64,
571 /// The sequence number carried by the frame.
572 got: u64,
573 },
574 /// The encoded frame is shorter than the minimum header length.
575 FrameTooLarge(usize),
576 /// The receive (or send) buffer for the stream is full.
577 BufferOverflow(u32),
578 /// A stream with the given id was already open when a SYN arrived.
579 StreamAlreadyOpen(u32),
580}
581
582impl fmt::Display for MuxError {
583 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584 match self {
585 Self::MaxStreamsReached => write!(f, "maximum number of streams reached"),
586 Self::StreamNotFound(id) => write!(f, "stream {id} not found"),
587 Self::StreamNotOpen(id) => write!(f, "stream {id} is not open"),
588 Self::WindowExhausted(id) => write!(f, "send window exhausted on stream {id}"),
589 Self::SequenceError { expected, got } => {
590 write!(f, "sequence error: expected {expected}, got {got}")
591 }
592 Self::FrameTooLarge(len) => write!(f, "frame data too short to decode ({len} bytes)"),
593 Self::BufferOverflow(id) => write!(f, "buffer overflow on stream {id}"),
594 Self::StreamAlreadyOpen(id) => write!(f, "stream {id} is already open"),
595 }
596 }
597}
598
599impl std::error::Error for MuxError {}
600
601// ─────────────────────────────────────────────────────────────────────────────
602// MuxStats — original compact statistics snapshot
603// ─────────────────────────────────────────────────────────────────────────────
604
605/// Compact snapshot of [`StreamMultiplexer`] statistics (original form).
606#[derive(Debug, Clone, PartialEq, Eq)]
607pub struct MuxStats {
608 /// Total streams ever opened (including currently open ones).
609 pub total_streams: usize,
610 /// Number of streams currently in the [`StreamState::Open`] state.
611 pub open_streams: usize,
612 /// Frames currently waiting in the send queue.
613 pub queued_frames: usize,
614 /// Total frames that have been dequeued and handed to the caller.
615 pub total_frames_sent: u64,
616 /// Total frames that have been processed via [`StreamMultiplexer::receive`].
617 pub total_frames_received: u64,
618 /// Total payload bytes that have been enqueued for sending across all streams.
619 pub total_bytes_sent: u64,
620}
621
622// ─────────────────────────────────────────────────────────────────────────────
623// StreamMultiplexer
624// ─────────────────────────────────────────────────────────────────────────────
625
626/// Multiplexes multiple logical streams over a single connection with
627/// flow control and priority-based scheduling.
628///
629/// # Example
630///
631/// ```rust
632/// use ipfrs_network::stream_multiplexer::{
633/// MultiplexerConfig, StreamMultiplexer, StreamPriority,
634/// };
635///
636/// let config = MultiplexerConfig::default();
637/// let mut mux = StreamMultiplexer::new(config);
638///
639/// let sid = mux.open_stream(StreamPriority::Normal).expect("open stream");
640/// let bytes = mux.send(sid, b"hello world".to_vec(), 0).expect("send");
641/// assert_eq!(bytes, 11);
642///
643/// // Drain all enqueued frames (SYN + data)
644/// let frames = mux.dequeue_frames(10);
645/// assert_eq!(frames.len(), 2);
646/// ```
647pub struct StreamMultiplexer {
648 /// Effective configuration for this multiplexer instance.
649 pub config: MultiplexerConfig,
650 /// All known streams, keyed by their id.
651 streams: HashMap<StreamId, LogicalStream>,
652 /// Priority send queue.
653 send_queue: BinaryHeap<PrioritizedFrame>,
654 /// Monotonically increasing stream id allocator.
655 next_stream_id: u32,
656 /// Global enqueue counter used for FIFO ordering within a priority tier.
657 enqueue_seq: u64,
658 /// Total frames dequeued (sent to the caller).
659 total_frames_sent: u64,
660 /// Total frames processed via `receive`.
661 total_frames_received: u64,
662 /// Total streams ever opened.
663 total_streams_opened: u64,
664 /// Frames dropped due to a full receive buffer.
665 dropped_frames: u64,
666}
667
668impl StreamMultiplexer {
669 // ─────────────────────────────────────────────────────────────
670 // Construction
671 // ─────────────────────────────────────────────────────────────
672
673 /// Create a new multiplexer with the given configuration.
674 pub fn new(config: MultiplexerConfig) -> Self {
675 Self {
676 streams: HashMap::with_capacity(config.max_streams.min(256)),
677 send_queue: BinaryHeap::new(),
678 next_stream_id: 1,
679 enqueue_seq: 0,
680 total_frames_sent: 0,
681 total_frames_received: 0,
682 total_streams_opened: 0,
683 dropped_frames: 0,
684 config,
685 }
686 }
687
688 // ─────────────────────────────────────────────────────────────
689 // Stream lifecycle
690 // ─────────────────────────────────────────────────────────────
691
692 /// Open a new logical stream with the specified priority.
693 ///
694 /// A SYN frame (flags = [`FLAG_SYN`]) is immediately enqueued.
695 ///
696 /// # Errors
697 ///
698 /// Returns [`MuxError::MaxStreamsReached`] if [`MultiplexerConfig::max_streams`]
699 /// streams are already open.
700 pub fn open_stream(&mut self, priority: StreamPriority) -> Result<StreamId, MuxError> {
701 if self.streams.len() >= self.config.max_streams {
702 return Err(MuxError::MaxStreamsReached);
703 }
704
705 let id = StreamId(self.next_stream_id);
706 self.next_stream_id = self.next_stream_id.wrapping_add(1);
707
708 let recv_cap = self.config.recv_buffer_capacity;
709 let stream = LogicalStream {
710 id,
711 priority,
712 state: StreamState::Open,
713 send_window: self.config.default_window_size,
714 recv_window: self.config.default_window_size,
715 send_seq: 1, // 0 is consumed by the SYN frame below
716 recv_seq: 0,
717 bytes_sent: 0,
718 bytes_received: 0,
719 frames_sent: 1, // SYN frame counted here
720 frames_received: 0,
721 opened_at: 0,
722 last_activity: 0,
723 priority_raw: priority.weight() as u8,
724 recv_buffer: VecDeque::with_capacity(recv_cap.min(64)),
725 recv_buffer_max: recv_cap,
726 };
727 self.streams.insert(id, stream);
728 self.total_streams_opened = self.total_streams_opened.wrapping_add(1);
729
730 // Enqueue SYN frame with sequence 0.
731 self.enqueue_control(id, FLAG_SYN, 0, priority, 0);
732
733 Ok(id)
734 }
735
736 /// Open a new logical stream with a raw priority byte (0-255).
737 ///
738 /// The raw byte is mapped to the nearest [`StreamPriority`] tier via
739 /// [`priority_from_u8`]. The raw value is preserved in [`LogicalStream::priority_raw`].
740 ///
741 /// # Errors
742 ///
743 /// Returns [`MuxError::MaxStreamsReached`] if the maximum stream count is exceeded.
744 pub fn open_stream_with_priority(&mut self, priority_raw: u8) -> Result<StreamId, MuxError> {
745 if self.streams.len() >= self.config.max_streams {
746 return Err(MuxError::MaxStreamsReached);
747 }
748
749 let priority = priority_from_u8(priority_raw);
750 let id = StreamId(self.next_stream_id);
751 self.next_stream_id = self.next_stream_id.wrapping_add(1);
752
753 let recv_cap = self.config.recv_buffer_capacity;
754 let stream = LogicalStream {
755 id,
756 priority,
757 state: StreamState::Open,
758 send_window: self.config.default_window_size,
759 recv_window: self.config.default_window_size,
760 send_seq: 1,
761 recv_seq: 0,
762 bytes_sent: 0,
763 bytes_received: 0,
764 frames_sent: 1,
765 frames_received: 0,
766 opened_at: 0,
767 last_activity: 0,
768 priority_raw,
769 recv_buffer: VecDeque::with_capacity(recv_cap.min(64)),
770 recv_buffer_max: recv_cap,
771 };
772 self.streams.insert(id, stream);
773 self.total_streams_opened = self.total_streams_opened.wrapping_add(1);
774
775 self.enqueue_control(id, FLAG_SYN, 0, priority, 0);
776
777 Ok(id)
778 }
779
780 /// Send a FIN frame and transition the stream to [`StreamState::HalfClosed`].
781 ///
782 /// # Errors
783 ///
784 /// - [`MuxError::StreamNotFound`] if the stream does not exist.
785 /// - [`MuxError::StreamNotOpen`] if the stream is already Closed or Reset.
786 pub fn close_stream(&mut self, stream_id: StreamId) -> Result<(), MuxError> {
787 let stream = self
788 .streams
789 .get_mut(&stream_id)
790 .ok_or(MuxError::StreamNotFound(stream_id.0))?;
791
792 match stream.state {
793 StreamState::Closed | StreamState::Reset => {
794 return Err(MuxError::StreamNotOpen(stream_id.0));
795 }
796 _ => {}
797 }
798
799 let seq = stream.send_seq;
800 stream.send_seq = stream.send_seq.wrapping_add(1);
801 stream.frames_sent = stream.frames_sent.wrapping_add(1);
802 let priority = stream.priority;
803 stream.state = StreamState::HalfClosed;
804
805 self.enqueue_control(stream_id, FLAG_FIN, seq, priority, 0);
806 Ok(())
807 }
808
809 /// Send an RST frame and transition the stream to [`StreamState::Reset`].
810 ///
811 /// # Errors
812 ///
813 /// - [`MuxError::StreamNotFound`] if the stream does not exist.
814 pub fn reset_stream(&mut self, stream_id: StreamId) -> Result<(), MuxError> {
815 let stream = self
816 .streams
817 .get_mut(&stream_id)
818 .ok_or(MuxError::StreamNotFound(stream_id.0))?;
819
820 let seq = stream.send_seq;
821 stream.send_seq = stream.send_seq.wrapping_add(1);
822 stream.frames_sent = stream.frames_sent.wrapping_add(1);
823 let priority = stream.priority;
824 stream.state = StreamState::Reset;
825
826 self.enqueue_control(stream_id, FLAG_RST, seq, priority, 0);
827 Ok(())
828 }
829
830 // ─────────────────────────────────────────────────────────────
831 // Data path
832 // ─────────────────────────────────────────────────────────────
833
834 /// Fragment `data` into frames of at most [`MultiplexerConfig::max_frame_size`]
835 /// bytes and enqueue them on the send queue.
836 ///
837 /// Returns the total number of bytes enqueued.
838 ///
839 /// # Errors
840 ///
841 /// - [`MuxError::StreamNotFound`] if the stream does not exist.
842 /// - [`MuxError::StreamNotOpen`] if the stream is not [`StreamState::Open`].
843 /// - [`MuxError::WindowExhausted`] if `data.len()` exceeds the remaining send window.
844 pub fn send(
845 &mut self,
846 stream_id: StreamId,
847 data: Vec<u8>,
848 now: u64,
849 ) -> Result<usize, MuxError> {
850 // Validate before mutating.
851 {
852 let stream = self
853 .streams
854 .get(&stream_id)
855 .ok_or(MuxError::StreamNotFound(stream_id.0))?;
856
857 if stream.state != StreamState::Open {
858 return Err(MuxError::StreamNotOpen(stream_id.0));
859 }
860
861 if data.len() as u64 > stream.send_window as u64 {
862 return Err(MuxError::WindowExhausted(stream_id.0));
863 }
864 }
865
866 let total = data.len();
867
868 // Fragment and enqueue.
869 let chunk_size = self.config.max_frame_size.max(1);
870 let mut offset = 0usize;
871
872 while offset < data.len() {
873 let end = (offset + chunk_size).min(data.len());
874 let chunk = data[offset..end].to_vec();
875 let chunk_len = chunk.len();
876
877 let stream = self
878 .streams
879 .get_mut(&stream_id)
880 .ok_or(MuxError::StreamNotFound(stream_id.0))?;
881
882 let seq = stream.send_seq;
883 stream.send_seq = stream.send_seq.wrapping_add(1);
884 stream.bytes_sent = stream.bytes_sent.saturating_add(chunk_len as u64);
885 stream.frames_sent = stream.frames_sent.wrapping_add(1);
886 // Deduct from send window immediately upon enqueue.
887 stream.send_window = stream.send_window.saturating_sub(chunk_len as u32);
888 stream.last_activity = now;
889 let priority = stream.priority;
890 let weight = if self.config.enable_priority {
891 priority.weight()
892 } else {
893 0
894 };
895
896 let enqueue_seq = self.enqueue_seq;
897 self.enqueue_seq = self.enqueue_seq.wrapping_add(1);
898
899 self.send_queue.push(PrioritizedFrame {
900 priority_weight: weight,
901 sequence: enqueue_seq,
902 frame: StreamFrame {
903 stream_id,
904 sequence: seq,
905 data: chunk,
906 flags: 0,
907 timestamp: now,
908 },
909 });
910
911 offset = end;
912 }
913
914 Ok(total)
915 }
916
917 /// Process an incoming frame from the remote peer.
918 ///
919 /// Returns a list of [`MuxEvent`]s describing what happened:
920 /// - SYN → [`MuxEvent::StreamOpened`] (opens the stream if not present)
921 /// - DATA → [`MuxEvent::FrameReceived`] (payload buffered in recv buffer)
922 /// - FIN → [`MuxEvent::StreamClosed`]
923 /// - RST → [`MuxEvent::StreamReset`]
924 /// - Recv buffer full → [`MuxEvent::SendBufferFull`] (frame dropped, counted in `dropped_frames`)
925 ///
926 /// # Errors
927 ///
928 /// - [`MuxError::StreamNotFound`] if the stream is unknown and frame is not SYN.
929 /// - [`MuxError::SequenceError`] if the frame arrives out of order.
930 pub fn receive_events(
931 &mut self,
932 frame: StreamFrame,
933 now: u64,
934 ) -> Result<Vec<MuxEvent>, MuxError> {
935 let mut events = Vec::new();
936
937 // SYN: open a new stream if it doesn't exist yet.
938 if frame.is_syn() {
939 if self.streams.contains_key(&frame.stream_id) {
940 return Err(MuxError::StreamAlreadyOpen(frame.stream_id.0));
941 }
942
943 if self.streams.len() >= self.config.max_streams {
944 return Err(MuxError::MaxStreamsReached);
945 }
946
947 let recv_cap = self.config.recv_buffer_capacity;
948 let stream = LogicalStream {
949 id: frame.stream_id,
950 priority: StreamPriority::Normal,
951 state: StreamState::Open,
952 send_window: self.config.default_window_size,
953 recv_window: self.config.default_window_size,
954 send_seq: 0,
955 recv_seq: 1, // SYN consumed sequence 0
956 bytes_sent: 0,
957 bytes_received: 0,
958 frames_sent: 0,
959 frames_received: 1,
960 opened_at: now,
961 last_activity: now,
962 priority_raw: StreamPriority::Normal.weight() as u8,
963 recv_buffer: VecDeque::with_capacity(recv_cap.min(64)),
964 recv_buffer_max: recv_cap,
965 };
966 self.streams.insert(frame.stream_id, stream);
967 self.total_streams_opened = self.total_streams_opened.wrapping_add(1);
968 self.total_frames_received = self.total_frames_received.wrapping_add(1);
969 events.push(MuxEvent::StreamOpened(frame.stream_id));
970 return Ok(events);
971 }
972
973 let stream = self
974 .streams
975 .get_mut(&frame.stream_id)
976 .ok_or(MuxError::StreamNotFound(frame.stream_id.0))?;
977
978 // RST takes immediate effect regardless of sequence.
979 if frame.is_rst() {
980 stream.state = StreamState::Reset;
981 stream.last_activity = now;
982 stream.frames_received = stream.frames_received.wrapping_add(1);
983 self.total_frames_received = self.total_frames_received.wrapping_add(1);
984 events.push(MuxEvent::StreamReset(frame.stream_id));
985 return Ok(events);
986 }
987
988 // Sequence check.
989 if frame.sequence != stream.recv_seq {
990 return Err(MuxError::SequenceError {
991 expected: stream.recv_seq,
992 got: frame.sequence,
993 });
994 }
995
996 stream.recv_seq = stream.recv_seq.wrapping_add(1);
997 let data_len = frame.data.len();
998 stream.frames_received = stream.frames_received.wrapping_add(1);
999 stream.last_activity = now;
1000 self.total_frames_received = self.total_frames_received.wrapping_add(1);
1001
1002 if frame.is_fin() {
1003 stream.bytes_received = stream.bytes_received.saturating_add(data_len as u64);
1004 stream.state = if stream.state == StreamState::HalfClosed {
1005 StreamState::Closed
1006 } else {
1007 StreamState::HalfClosed
1008 };
1009 events.push(MuxEvent::StreamClosed(frame.stream_id));
1010 } else if !frame.data.is_empty() {
1011 // DATA frame: buffer the payload.
1012 stream.bytes_received = stream.bytes_received.saturating_add(data_len as u64);
1013 if stream.recv_buffer.len() >= stream.recv_buffer_max {
1014 self.dropped_frames = self.dropped_frames.wrapping_add(1);
1015 events.push(MuxEvent::SendBufferFull(frame.stream_id));
1016 } else {
1017 stream.recv_buffer.push_back(frame.data);
1018 events.push(MuxEvent::FrameReceived(frame.stream_id));
1019 }
1020 } else {
1021 events.push(MuxEvent::FrameReceived(frame.stream_id));
1022 }
1023
1024 Ok(events)
1025 }
1026
1027 /// Process an incoming frame from the remote peer (original API).
1028 ///
1029 /// Handles RST, FIN, and data frames. Returns the payload bytes.
1030 ///
1031 /// # Errors
1032 ///
1033 /// - [`MuxError::StreamNotFound`] if the stream is unknown.
1034 /// - [`MuxError::SequenceError`] if the frame arrives out of order.
1035 pub fn receive(&mut self, frame: StreamFrame, _now: u64) -> Result<Vec<u8>, MuxError> {
1036 let stream = self
1037 .streams
1038 .get_mut(&frame.stream_id)
1039 .ok_or(MuxError::StreamNotFound(frame.stream_id.0))?;
1040
1041 // RST takes immediate effect regardless of sequence.
1042 if frame.is_rst() {
1043 stream.state = StreamState::Reset;
1044 self.total_frames_received = self.total_frames_received.wrapping_add(1);
1045 return Ok(Vec::new());
1046 }
1047
1048 // Sequence check (SYN frames reset the counter expectation).
1049 if !frame.is_syn() && frame.sequence != stream.recv_seq {
1050 return Err(MuxError::SequenceError {
1051 expected: stream.recv_seq,
1052 got: frame.sequence,
1053 });
1054 }
1055
1056 stream.recv_seq = stream.recv_seq.wrapping_add(1);
1057 let data_len = frame.data.len();
1058 stream.bytes_received = stream.bytes_received.saturating_add(data_len as u64);
1059
1060 if frame.is_fin() {
1061 stream.state = StreamState::HalfClosed;
1062 }
1063
1064 self.total_frames_received = self.total_frames_received.wrapping_add(1);
1065 Ok(frame.data)
1066 }
1067
1068 // ─────────────────────────────────────────────────────────────
1069 // Receive buffer drain
1070 // ─────────────────────────────────────────────────────────────
1071
1072 /// Drain all buffered receive payloads for `stream_id`.
1073 ///
1074 /// Returns a `Vec` of payload byte vectors in arrival order.
1075 /// The receive buffer is emptied after this call.
1076 ///
1077 /// # Errors
1078 ///
1079 /// Returns [`MuxError::StreamNotFound`] if the stream does not exist.
1080 pub fn drain_recv_buffer(&mut self, stream_id: StreamId) -> Result<Vec<Vec<u8>>, MuxError> {
1081 let stream = self
1082 .streams
1083 .get_mut(&stream_id)
1084 .ok_or(MuxError::StreamNotFound(stream_id.0))?;
1085
1086 let payloads: Vec<Vec<u8>> = stream.recv_buffer.drain(..).collect();
1087 Ok(payloads)
1088 }
1089
1090 // ─────────────────────────────────────────────────────────────
1091 // Dequeue
1092 // ─────────────────────────────────────────────────────────────
1093
1094 /// Pop the highest-priority frame from the send queue.
1095 ///
1096 /// Updates `total_frames_sent`. For data frames (no control flags),
1097 /// the stream's `send_window` has already been decremented in `send`;
1098 /// no further deduction is needed here.
1099 pub fn dequeue_frame(&mut self) -> Option<StreamFrame> {
1100 let pf = self.send_queue.pop()?;
1101 self.total_frames_sent = self.total_frames_sent.wrapping_add(1);
1102 Some(pf.frame)
1103 }
1104
1105 /// Pop up to `n` frames from the send queue in priority order.
1106 pub fn dequeue_frames(&mut self, n: usize) -> Vec<StreamFrame> {
1107 let mut result = Vec::with_capacity(n);
1108 for _ in 0..n {
1109 match self.dequeue_frame() {
1110 Some(f) => result.push(f),
1111 None => break,
1112 }
1113 }
1114 result
1115 }
1116
1117 /// Drain **all** pending outbound frames across all streams in priority order.
1118 ///
1119 /// Equivalent to calling `dequeue_frames` with `usize::MAX`, but more
1120 /// efficient since it drains the heap directly.
1121 pub fn drain_send_queue(&mut self) -> Vec<StreamFrame> {
1122 let mut result = Vec::with_capacity(self.send_queue.len());
1123 while let Some(pf) = self.send_queue.pop() {
1124 self.total_frames_sent = self.total_frames_sent.wrapping_add(1);
1125 result.push(pf.frame);
1126 }
1127 result
1128 }
1129
1130 // ─────────────────────────────────────────────────────────────
1131 // Idle expiry
1132 // ─────────────────────────────────────────────────────────────
1133
1134 /// Expire streams that have been idle longer than
1135 /// [`MultiplexerConfig::idle_timeout_us`].
1136 ///
1137 /// A stream is considered idle if
1138 /// `last_activity + idle_timeout_us < current_ts`.
1139 ///
1140 /// Expired streams are transitioned to [`StreamState::Closed`] and a
1141 /// [`MuxEvent::IdleStreamExpired`] event is emitted for each one.
1142 pub fn expire_idle(&mut self, current_ts: u64) -> Vec<MuxEvent> {
1143 let timeout = self.config.idle_timeout_us;
1144 let mut expired = Vec::new();
1145
1146 for (id, stream) in self.streams.iter_mut() {
1147 if matches!(
1148 stream.state,
1149 StreamState::Open | StreamState::Opening | StreamState::HalfClosed
1150 ) && stream.last_activity.saturating_add(timeout) < current_ts
1151 {
1152 stream.state = StreamState::Closed;
1153 expired.push(*id);
1154 }
1155 }
1156
1157 expired
1158 .into_iter()
1159 .map(MuxEvent::IdleStreamExpired)
1160 .collect()
1161 }
1162
1163 // ─────────────────────────────────────────────────────────────
1164 // Flow control
1165 // ─────────────────────────────────────────────────────────────
1166
1167 /// Add `increment` bytes to the send window for `stream_id`.
1168 ///
1169 /// Returns `true` if the stream exists and the window was updated,
1170 /// `false` otherwise.
1171 pub fn update_window(&mut self, stream_id: StreamId, increment: u32) -> bool {
1172 if let Some(stream) = self.streams.get_mut(&stream_id) {
1173 stream.send_window = stream.send_window.saturating_add(increment);
1174 true
1175 } else {
1176 false
1177 }
1178 }
1179
1180 // ─────────────────────────────────────────────────────────────
1181 // Introspection
1182 // ─────────────────────────────────────────────────────────────
1183
1184 /// Return a reference to the [`LogicalStream`] for the given id, or `None`.
1185 pub fn stream_state(&self, stream_id: StreamId) -> Option<&LogicalStream> {
1186 self.streams.get(&stream_id)
1187 }
1188
1189 /// Return a read-only [`StreamInfo`] snapshot for the given stream.
1190 ///
1191 /// # Errors
1192 ///
1193 /// Returns [`MuxError::StreamNotFound`] if the stream does not exist.
1194 pub fn stream_info(&self, stream_id: StreamId) -> Result<StreamInfo, MuxError> {
1195 let s = self
1196 .streams
1197 .get(&stream_id)
1198 .ok_or(MuxError::StreamNotFound(stream_id.0))?;
1199
1200 Ok(StreamInfo {
1201 id: s.id,
1202 state: s.state,
1203 bytes_sent: s.bytes_sent,
1204 bytes_received: s.bytes_received,
1205 frames_sent: s.frames_sent,
1206 frames_received: s.frames_received,
1207 opened_at: s.opened_at,
1208 last_activity: s.last_activity,
1209 priority: s.priority_raw,
1210 })
1211 }
1212
1213 /// Return the ids of all currently active (Open or Opening) streams.
1214 pub fn active_streams(&self) -> Vec<StreamId> {
1215 self.streams
1216 .values()
1217 .filter(|s| matches!(s.state, StreamState::Open | StreamState::Opening))
1218 .map(|s| s.id)
1219 .collect()
1220 }
1221
1222 /// Number of streams currently in [`StreamState::Open`].
1223 pub fn open_stream_count(&self) -> usize {
1224 self.streams
1225 .values()
1226 .filter(|s| s.state == StreamState::Open)
1227 .count()
1228 }
1229
1230 /// Return the original compact statistics snapshot ([`MuxStats`]).
1231 pub fn stats(&self) -> MuxStats {
1232 let total_bytes_sent = self
1233 .streams
1234 .values()
1235 .map(|s| s.bytes_sent)
1236 .fold(0u64, |acc, b| acc.saturating_add(b));
1237
1238 MuxStats {
1239 total_streams: self.streams.len(),
1240 open_streams: self.open_stream_count(),
1241 queued_frames: self.send_queue.len(),
1242 total_frames_sent: self.total_frames_sent,
1243 total_frames_received: self.total_frames_received,
1244 total_bytes_sent,
1245 }
1246 }
1247
1248 /// Return the richer [`MultiplexerStats`] snapshot.
1249 pub fn multiplexer_stats(&self) -> MultiplexerStats {
1250 let (total_bytes_sent, total_bytes_received) =
1251 self.streams.values().fold((0u64, 0u64), |(s, r), stream| {
1252 (
1253 s.saturating_add(stream.bytes_sent),
1254 r.saturating_add(stream.bytes_received),
1255 )
1256 });
1257
1258 let active = self
1259 .streams
1260 .values()
1261 .filter(|s| matches!(s.state, StreamState::Open | StreamState::Opening))
1262 .count();
1263
1264 MultiplexerStats {
1265 active_streams: active,
1266 total_streams_opened: self.total_streams_opened,
1267 total_frames_sent: self.total_frames_sent,
1268 total_frames_received: self.total_frames_received,
1269 total_bytes_sent,
1270 total_bytes_received,
1271 dropped_frames: self.dropped_frames,
1272 }
1273 }
1274
1275 // ─────────────────────────────────────────────────────────────
1276 // Private helpers
1277 // ─────────────────────────────────────────────────────────────
1278
1279 /// Enqueue a control frame (SYN / FIN / RST) without touching the send window.
1280 fn enqueue_control(
1281 &mut self,
1282 stream_id: StreamId,
1283 flags: u8,
1284 sequence: u64,
1285 priority: StreamPriority,
1286 now: u64,
1287 ) {
1288 let weight = if self.config.enable_priority {
1289 priority.weight()
1290 } else {
1291 0
1292 };
1293 let enqueue_seq = self.enqueue_seq;
1294 self.enqueue_seq = self.enqueue_seq.wrapping_add(1);
1295
1296 self.send_queue.push(PrioritizedFrame {
1297 priority_weight: weight,
1298 sequence: enqueue_seq,
1299 frame: StreamFrame {
1300 stream_id,
1301 sequence,
1302 data: Vec::new(),
1303 flags,
1304 timestamp: now,
1305 },
1306 });
1307 }
1308}
1309
1310// ─────────────────────────────────────────────────────────────────────────────
1311// PRNG helper (no external rand dependency)
1312// ─────────────────────────────────────────────────────────────────────────────
1313
1314/// Xorshift64 PRNG — seedable, dependency-free random number source for tests.
1315///
1316/// # Example
1317/// ```ignore
1318/// let mut state = 12345u64;
1319/// let r = xorshift64(&mut state);
1320/// ```
1321pub fn xorshift64(state: &mut u64) -> u64 {
1322 let mut x = *state;
1323 x ^= x << 13;
1324 x ^= x >> 7;
1325 x ^= x << 17;
1326 *state = x;
1327 x
1328}
1329
1330// ─────────────────────────────────────────────────────────────────────────────
1331// Tests (split to stream_multiplexer_tests.rs to keep this file < 2000 lines)
1332// ─────────────────────────────────────────────────────────────────────────────
1333
1334#[cfg(test)]
1335#[path = "stream_multiplexer_tests.rs"]
1336mod tests;