Skip to main content

grpc_core/
slot_map.rs

1//! Bounded, single-threaded ownership of HTTP/2 stream receive events.
2//!
3//! Budgets count metadata, message descriptors and wire bytes, excluding allocator overhead.
4//! Normalized buffers and shrinking queues bound spare capacity. Rejections preserve accepted data.
5use std::{
6    collections::{BTreeMap, VecDeque},
7    mem::size_of,
8};
9
10/// Ordered raw HTTP/2 metadata, including protocol keys such as `grpc-status`.
11pub type Headers = Vec<(String, Vec<u8>)>;
12
13/// One complete gRPC envelope; decompression belongs to the codec boundary.
14#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct StreamMessage {
16    /// Whether the payload uses the negotiated message compression algorithm.
17    pub compressed: bool,
18    /// Encoded message bytes without the five-byte envelope.
19    pub payload: Vec<u8>,
20}
21
22/// Independent receive limits applied to every stream.
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct StreamLimits {
25    /// Maximum retained bytes, including metadata and message descriptors.
26    pub max_buffered_bytes: usize,
27    /// Maximum complete messages awaiting consumption, including empty messages.
28    pub max_queued_messages: usize,
29}
30impl Default for StreamLimits {
31    fn default() -> Self {
32        Self {
33            max_buffered_bytes: 8 * 1024 * 1024,
34            max_queued_messages: 1024,
35        }
36    }
37}
38
39/// A rejected stream operation. Rejection never consumes an input prefix.
40#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
41pub enum SlotMapError {
42    /// HTTP/2 stream identifiers must be positive signed 31-bit integers.
43    #[error("invalid stream identifier {0}")]
44    InvalidStreamId(i32),
45    /// The identifier already has a slot.
46    #[error("stream {0} is already open")]
47    AlreadyOpen(i32),
48    /// The identifier has no slot.
49    #[error("unknown stream {0}")]
50    UnknownStream(i32),
51    /// The peer's GOAWAY prohibits this stream.
52    #[error("GOAWAY: stream={stream_id}, last={last_stream_id}, code={error_code}")]
53    GoAway {
54        stream_id: i32,
55        last_stream_id: i32,
56        error_code: u32,
57    },
58    /// An error or incomplete-response reset prevents reads of buffered events.
59    #[error("stream {stream_id} reset: code={error_code}")]
60    Reset { stream_id: i32, error_code: u32 },
61    /// The event violates the receive or half-close lifecycle.
62    #[error("stream {stream_id}: cannot {operation}")]
63    InvalidTransition {
64        stream_id: i32,
65        operation: &'static str,
66    },
67    /// The stream would exceed its retained byte budget.
68    #[error("stream {stream_id}: byte limit {limit}")]
69    MemoryLimit { stream_id: i32, limit: usize },
70    /// The stream would exceed its queued message count.
71    #[error("stream {stream_id}: message limit {limit}")]
72    MessageLimit { stream_id: i32, limit: usize },
73    /// A gRPC envelope compression flag was neither zero nor one.
74    #[error("stream {stream_id}: invalid compression flag {flag}")]
75    InvalidCompressionFlag { stream_id: i32, flag: u8 },
76    /// Trailers or END_STREAM arrived in an incomplete envelope.
77    #[error("stream {0}: truncated envelope")]
78    TruncatedFrame(i32),
79    /// Both halves must close, or a reset must arrive, before removal.
80    #[error("stream {0} is not closed")]
81    NotClosed(i32),
82}
83/// Result of admitting, consuming or transitioning a stream event.
84pub type SlotResult<T> = Result<T, SlotMapError>;
85fn memory_limit(stream_id: i32, limits: StreamLimits) -> SlotMapError {
86    SlotMapError::MemoryLimit {
87        stream_id,
88        limit: limits.max_buffered_bytes,
89    }
90}
91fn transition(stream_id: i32, operation: &'static str) -> SlotMapError {
92    SlotMapError::InvalidTransition {
93        stream_id,
94        operation,
95    }
96}
97
98/// Receive queues and independent local/remote lifecycle state for one stream.
99#[derive(Debug, Default)]
100pub struct StreamSlot {
101    initial_metadata: Option<Headers>,
102    initial_seen: bool,
103    initial_bytes: usize,
104    messages: VecDeque<StreamMessage>,
105    trailers: Option<Headers>,
106    trailers_seen: bool,
107    trailer_bytes: usize,
108    partial: Vec<u8>,
109    buffered_bytes: usize,
110    remote_end: bool,
111    local_end: bool,
112    reset: Option<u32>,
113}
114impl StreamSlot {
115    /// Retained bytes charged to this stream's budget.
116    #[must_use]
117    pub const fn buffered_bytes(&self) -> usize {
118        self.buffered_bytes
119    }
120    /// Complete messages currently awaiting consumption.
121    #[must_use]
122    pub fn queued_message_count(&self) -> usize {
123        self.messages.len()
124    }
125    /// Whether the peer has closed its sending half.
126    #[must_use]
127    pub const fn remote_end_stream(&self) -> bool {
128        self.remote_end
129    }
130    /// Whether the local endpoint has closed its sending half.
131    #[must_use]
132    pub const fn local_end_stream(&self) -> bool {
133        self.local_end
134    }
135    /// The HTTP/2 RST_STREAM error code, if received.
136    #[must_use]
137    pub const fn reset_error_code(&self) -> Option<u32> {
138        self.reset
139    }
140    fn check_reset(&self, stream_id: i32) -> SlotResult<()> {
141        match self.reset {
142            Some(0) if self.trailers_seen && self.remote_end => Ok(()),
143            Some(error_code) => Err(SlotMapError::Reset {
144                stream_id,
145                error_code,
146            }),
147            None => Ok(()),
148        }
149    }
150    fn receive(&self, stream_id: i32, operation: &'static str) -> SlotResult<()> {
151        self.check_reset(stream_id)?;
152        if self.remote_end || self.trailers_seen {
153            return Err(transition(stream_id, operation));
154        }
155        Ok(())
156    }
157    fn budget(&self, id: i32, bytes: usize, limits: StreamLimits) -> SlotResult<usize> {
158        self.buffered_bytes
159            .checked_add(bytes)
160            .filter(|total| *total <= limits.max_buffered_bytes)
161            .ok_or(memory_limit(id, limits))
162    }
163}
164
165/// The local HTTP/2 endpoint determines which stream identifiers it initiates.
166#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
167pub enum EndpointRole {
168    /// Clients initiate odd-numbered streams.
169    #[default]
170    Client,
171    /// Servers initiate even-numbered streams.
172    Server,
173}
174impl EndpointRole {
175    const fn initiates(self, id: i32) -> bool {
176        matches!(self, Self::Client) == (id % 2 == 1)
177    }
178}
179/// A synchronous stream demultiplexer with explicit ownership transfer on reads.
180#[derive(Debug, Default)]
181pub struct StreamSlotMap {
182    slots: BTreeMap<i32, StreamSlot>,
183    limits: StreamLimits,
184    role: EndpointRole,
185    goaway: Option<(i32, u32)>,
186}
187impl StreamSlotMap {
188    /// Creates a client map. Zero limits intentionally disable buffering.
189    #[must_use]
190    pub const fn new(limits: StreamLimits) -> Self {
191        Self::with_role(limits, EndpointRole::Client)
192    }
193    /// Creates an empty map for the specified local HTTP/2 endpoint role.
194    #[must_use]
195    pub const fn with_role(limits: StreamLimits, role: EndpointRole) -> Self {
196        Self {
197            slots: BTreeMap::new(),
198            limits,
199            role,
200            goaway: None,
201        }
202    }
203    /// Opens a stream without allocating receive buffers.
204    /// # Errors
205    /// Rejects invalid/existing identifiers and new local streams after GOAWAY.
206    pub fn open_stream(&mut self, stream_id: i32) -> SlotResult<()> {
207        if stream_id <= 0 {
208            return Err(SlotMapError::InvalidStreamId(stream_id));
209        }
210        if let Some((last_stream_id, error_code)) = self.goaway
211            && self.role.initiates(stream_id)
212        {
213            return Err(SlotMapError::GoAway {
214                stream_id,
215                last_stream_id,
216                error_code,
217            });
218        }
219        if self.slots.contains_key(&stream_id) {
220            return Err(SlotMapError::AlreadyOpen(stream_id));
221        }
222        self.slots.insert(stream_id, StreamSlot::default());
223        Ok(())
224    }
225    /// Looks up a stream without consuming events.
226    #[must_use]
227    pub fn get(&self, stream_id: i32) -> Option<&StreamSlot> {
228        self.slots.get(&stream_id)
229    }
230    /// Number of retained stream slots, including closed slots awaiting removal.
231    #[must_use]
232    pub fn active_stream_count(&self) -> usize {
233        self.slots.len()
234    }
235    /// Most restrictive GOAWAY boundary and its associated HTTP/2 error code.
236    #[must_use]
237    pub const fn goaway(&self) -> Option<(i32, u32)> {
238        self.goaway
239    }
240    /// Stores the initial header block exactly once, even after it is consumed.
241    /// # Errors
242    /// Rejects unknown/reset streams, duplicate or late headers, and budget excess.
243    pub fn push_initial_metadata(&mut self, stream_id: i32, headers: Headers) -> SlotResult<()> {
244        self.push_headers(stream_id, headers, false)
245    }
246    /// Accepts arbitrary DATA fragmentation and queues complete gRPC envelopes.
247    /// # Errors
248    /// Rejects missing headers, ended/reset streams, malformed flags or excess resources.
249    /// Rejections preserve accepted frames and partial bytes.
250    pub fn append_recv_bytes(&mut self, stream_id: i32, bytes: &[u8]) -> SlotResult<()> {
251        let limits = self.limits;
252        let slot = self.slot_mut(stream_id)?;
253        slot.receive(stream_id, "receive DATA")?;
254        if !slot.initial_seen {
255            return Err(transition(
256                stream_id,
257                "receive DATA before initial metadata",
258            ));
259        }
260        let mut total = slot.budget(stream_id, bytes.len(), limits)?;
261        let len = slot.partial.len() + bytes.len();
262        let at = |index: usize| {
263            if index < slot.partial.len() {
264                slot.partial[index]
265            } else {
266                bytes[index - slot.partial.len()]
267            }
268        };
269        let mut consumed = 0;
270        let mut count = 0;
271        while consumed < len {
272            let flag = at(consumed);
273            if flag > 1 {
274                return Err(SlotMapError::InvalidCompressionFlag { stream_id, flag });
275            }
276            if len - consumed < 5 {
277                break;
278            }
279            let payload_len =
280                u32::from_be_bytes(std::array::from_fn(|i| at(consumed + 1 + i))) as usize;
281            let frame_len = payload_len
282                .checked_add(5)
283                .ok_or(memory_limit(stream_id, limits))?;
284            if frame_len
285                .checked_add(size_of::<StreamMessage>())
286                .is_none_or(|required| required > limits.max_buffered_bytes)
287            {
288                return Err(memory_limit(stream_id, limits));
289            }
290            if len - consumed < frame_len {
291                break;
292            }
293            count += 1;
294            if slot
295                .messages
296                .len()
297                .checked_add(count)
298                .is_none_or(|n| n > limits.max_queued_messages)
299            {
300                return Err(SlotMapError::MessageLimit {
301                    stream_id,
302                    limit: limits.max_queued_messages,
303                });
304            }
305            total = total
306                .checked_add(size_of::<StreamMessage>())
307                .filter(|n| *n <= limits.max_buffered_bytes)
308                .ok_or(memory_limit(stream_id, limits))?;
309            consumed += frame_len;
310        }
311        if count == 0 {
312            slot.partial.extend_from_slice(bytes);
313            slot.buffered_bytes = total;
314            return Ok(());
315        }
316        let mut combined = Vec::with_capacity(len);
317        combined.extend_from_slice(&slot.partial);
318        combined.extend_from_slice(bytes);
319        let mut offset = 0;
320        for _ in 0..count {
321            let payload_len =
322                u32::from_be_bytes(std::array::from_fn(|i| combined[offset + 1 + i])) as usize;
323            let end = offset + 5 + payload_len;
324            slot.messages.push_back(StreamMessage {
325                compressed: combined[offset] == 1,
326                payload: combined[offset + 5..end].to_vec(),
327            });
328            offset = end;
329        }
330        slot.partial = combined[consumed..].to_vec();
331        slot.buffered_bytes = total;
332        Ok(())
333    }
334    /// Queues a complete envelope supplied by an upstream frame decoder.
335    /// # Errors
336    /// Rejects missing headers, ended/reset streams, partial frames or excess resources.
337    pub fn push_message(&mut self, stream_id: i32, message: StreamMessage) -> SlotResult<()> {
338        let limits = self.limits;
339        let slot = self.slot_mut(stream_id)?;
340        slot.receive(stream_id, "receive a message")?;
341        if !slot.initial_seen || !slot.partial.is_empty() {
342            return Err(transition(
343                stream_id,
344                "receive decoded message before headers or within a partial frame",
345            ));
346        }
347        if slot.messages.len() >= limits.max_queued_messages {
348            return Err(SlotMapError::MessageLimit {
349                stream_id,
350                limit: limits.max_queued_messages,
351            });
352        }
353        let additional = message
354            .payload
355            .len()
356            .checked_add(5 + size_of::<StreamMessage>())
357            .ok_or(memory_limit(stream_id, limits))?;
358        let total = slot.budget(stream_id, additional, limits)?;
359        slot.messages.push_back(StreamMessage {
360            compressed: message.compressed,
361            payload: message.payload.into_boxed_slice().into_vec(),
362        });
363        slot.buffered_bytes = total;
364        Ok(())
365    }
366    /// Stores trailers or a Trailers-Only response; mark END_STREAM separately.
367    /// # Errors
368    /// Rejects ended/reset streams, duplicate trailers, partial frames or excess resources.
369    pub fn push_trailing_metadata(&mut self, stream_id: i32, headers: Headers) -> SlotResult<()> {
370        self.push_headers(stream_id, headers, true)
371    }
372    fn push_headers(&mut self, stream_id: i32, headers: Headers, trailing: bool) -> SlotResult<()> {
373        let limits = self.limits;
374        let slot = self.slot_mut(stream_id)?;
375        slot.receive(stream_id, "receive metadata")?;
376        if !trailing && slot.initial_seen {
377            return Err(transition(stream_id, "receive duplicate initial metadata"));
378        }
379        if trailing && !slot.partial.is_empty() {
380            return Err(SlotMapError::TruncatedFrame(stream_id));
381        }
382        let additional = header_bytes(&headers).ok_or(memory_limit(stream_id, limits))?;
383        let total = slot.budget(stream_id, additional, limits)?;
384        if trailing {
385            slot.trailer_bytes = additional;
386            slot.trailers = Some(normalize_headers(headers));
387            slot.trailers_seen = true;
388        } else {
389            slot.initial_bytes = additional;
390            slot.initial_metadata = Some(normalize_headers(headers));
391            slot.initial_seen = true;
392        }
393        slot.buffered_bytes = total;
394        Ok(())
395    }
396    /// Closes the peer's sending half; request streams need not carry trailers.
397    /// # Errors
398    /// Rejects unknown/reset streams, duplicate END_STREAM and truncated envelopes.
399    pub fn mark_remote_end_stream(&mut self, stream_id: i32) -> SlotResult<()> {
400        let slot = self.slot_mut(stream_id)?;
401        slot.check_reset(stream_id)?;
402        if slot.remote_end {
403            return Err(transition(stream_id, "receive duplicate END_STREAM"));
404        }
405        if !slot.partial.is_empty() {
406            return Err(SlotMapError::TruncatedFrame(stream_id));
407        }
408        slot.remote_end = true;
409        Ok(())
410    }
411    /// Closes the local sending half independently of the receive queues.
412    /// # Errors
413    /// Rejects unknown/reset streams or duplicate local END_STREAM.
414    pub fn mark_local_end_stream(&mut self, stream_id: i32) -> SlotResult<()> {
415        let slot = self.slot_mut(stream_id)?;
416        slot.check_reset(stream_id)?;
417        if slot.local_end {
418            return Err(transition(stream_id, "send duplicate END_STREAM"));
419        }
420        slot.local_end = true;
421        Ok(())
422    }
423    /// Closes both halves, preserving completed responses for NO_ERROR resets.
424    /// # Errors
425    /// Rejects unknown streams and repeated resets, preserving the first code.
426    pub fn record_rst_stream(&mut self, stream_id: i32, error_code: u32) -> SlotResult<()> {
427        let slot = self.slot_mut(stream_id)?;
428        if let Some(error_code) = slot.reset {
429            return Err(SlotMapError::Reset {
430                stream_id,
431                error_code,
432            });
433        }
434        if error_code == 0 && slot.trailers_seen && slot.remote_end {
435            slot.reset = Some(0);
436            slot.local_end = true;
437            return Ok(());
438        }
439        *slot = StreamSlot {
440            reset: Some(error_code),
441            remote_end: true,
442            local_end: true,
443            ..StreamSlot::default()
444        };
445        Ok(())
446    }
447    /// Prunes excluded local streams; peer-initiated streams remain unaffected.
448    /// Repeated GOAWAY frames can only reduce the accepted stream boundary.
449    /// # Errors
450    /// Rejects negative boundaries and increases to an established boundary.
451    pub fn apply_goaway(&mut self, last_stream_id: i32, error_code: u32) -> SlotResult<Vec<i32>> {
452        if last_stream_id < 0 {
453            return Err(SlotMapError::InvalidStreamId(last_stream_id));
454        }
455        if self
456            .goaway
457            .is_some_and(|(previous, _)| last_stream_id > previous)
458        {
459            return Err(transition(last_stream_id, "increase GOAWAY boundary"));
460        }
461        let mut removed = Vec::new();
462        self.slots.retain(|id, _| {
463            let excluded = *id > last_stream_id && self.role.initiates(*id);
464            if excluded {
465                removed.push(*id);
466            }
467            !excluded
468        });
469        self.goaway = Some((last_stream_id, error_code));
470        Ok(removed)
471    }
472    /// Transfers initial metadata ownership, leaving lifecycle history intact.
473    /// # Errors
474    /// Rejects unknown streams or resets other than completed-response NO_ERROR.
475    pub fn pop_initial_metadata(&mut self, stream_id: i32) -> SlotResult<Option<Headers>> {
476        let slot = self.slot_mut(stream_id)?;
477        slot.check_reset(stream_id)?;
478        let headers = slot.initial_metadata.take();
479        slot.buffered_bytes -= std::mem::take(&mut slot.initial_bytes);
480        Ok(headers)
481    }
482    /// Transfers the next message in stream order and releases its byte charge.
483    /// # Errors
484    /// Rejects unknown streams or resets other than completed-response NO_ERROR.
485    pub fn pop_message(&mut self, stream_id: i32) -> SlotResult<Option<StreamMessage>> {
486        let slot = self.slot_mut(stream_id)?;
487        slot.check_reset(stream_id)?;
488        let message = slot.messages.pop_front();
489        if slot.messages.len() <= slot.messages.capacity() / 4 {
490            slot.messages.shrink_to_fit();
491        }
492        if let Some(message) = &message {
493            slot.buffered_bytes -= message.payload.len() + 5 + size_of::<StreamMessage>();
494        }
495        Ok(message)
496    }
497    /// Transfers trailers exactly once without changing the remote half state.
498    /// # Errors
499    /// Rejects unknown streams or resets other than completed-response NO_ERROR.
500    pub fn take_trailers(&mut self, stream_id: i32) -> SlotResult<Option<Headers>> {
501        let slot = self.slot_mut(stream_id)?;
502        slot.check_reset(stream_id)?;
503        let headers = slot.trailers.take();
504        slot.buffered_bytes -= std::mem::take(&mut slot.trailer_bytes);
505        Ok(headers)
506    }
507    /// Removes a reset or fully closed stream, including any unread events.
508    /// # Errors
509    /// Rejects unknown streams and streams with an open sending half.
510    pub fn remove_closed_stream(&mut self, stream_id: i32) -> SlotResult<StreamSlot> {
511        let slot = self
512            .slots
513            .get(&stream_id)
514            .ok_or(SlotMapError::UnknownStream(stream_id))?;
515        if slot.reset.is_none() && !(slot.remote_end && slot.local_end) {
516            return Err(SlotMapError::NotClosed(stream_id));
517        }
518        self.slots
519            .remove(&stream_id)
520            .ok_or(SlotMapError::UnknownStream(stream_id))
521    }
522    fn slot_mut(&mut self, id: i32) -> SlotResult<&mut StreamSlot> {
523        self.slots
524            .get_mut(&id)
525            .ok_or(SlotMapError::UnknownStream(id))
526    }
527}
528fn header_bytes(headers: &Headers) -> Option<usize> {
529    headers.iter().try_fold(0_usize, |total, (key, value)| {
530        total
531            .checked_add(size_of::<(String, Vec<u8>)>())?
532            .checked_add(key.len())?
533            .checked_add(value.len())
534    })
535}
536fn normalize_headers(headers: Headers) -> Headers {
537    headers
538        .into_iter()
539        .map(|(key, value)| (key.into_boxed_str().into(), value.into_boxed_slice().into()))
540        .collect::<Box<[_]>>()
541        .into_vec()
542}
543#[cfg(test)]
544mod tests {
545    use super::*;
546    fn headers(name: &str, value: &[u8]) -> Headers {
547        vec![(name.to_owned(), value.to_vec())]
548    }
549    fn message(payload: &[u8]) -> StreamMessage {
550        StreamMessage {
551            compressed: false,
552            payload: payload.to_vec(),
553        }
554    }
555    fn frame(payload: &[u8]) -> Vec<u8> {
556        let mut wire = vec![0];
557        let len = u32::try_from(payload.len()).expect("test length");
558        wire.extend(len.to_be_bytes());
559        wire.extend(payload);
560        wire
561    }
562    fn bounded(bytes: usize, messages: usize) -> StreamSlotMap {
563        StreamSlotMap::new(StreamLimits {
564            max_buffered_bytes: bytes,
565            max_queued_messages: messages,
566        })
567    }
568    fn open(map: &mut StreamSlotMap, id: i32) -> SlotResult<()> {
569        map.open_stream(id)?;
570        map.push_initial_metadata(id, vec![])
571    }
572    #[test]
573    fn interleaved_streams_preserve_all_events() -> SlotResult<()> {
574        let mut map = StreamSlotMap::default();
575        let initial = headers(":status", b"200");
576        for id in [1, 3] {
577            map.open_stream(id)?;
578            map.push_initial_metadata(id, initial.clone())?;
579        }
580        map.push_message(3, message(b"three-a"))?;
581        map.push_message(1, message(b"one"))?;
582        map.push_message(3, message(b"three-b"))?;
583        assert_eq!(map.pop_message(1)?, Some(message(b"one")));
584        for payload in [b"three-a", b"three-b"] {
585            assert_eq!(map.pop_message(3)?, Some(message(payload)));
586        }
587        for id in [1, 3] {
588            map.push_trailing_metadata(id, headers("grpc-status", b"0"))?;
589            map.mark_remote_end_stream(id)?;
590            assert_eq!(map.pop_initial_metadata(id)?, Some(initial.clone()));
591            assert_eq!(map.take_trailers(id)?, Some(headers("grpc-status", b"0")));
592        }
593        Ok(())
594    }
595    #[test]
596    fn trailers_only_and_duplicate_metadata() -> SlotResult<()> {
597        let mut map = StreamSlotMap::default();
598        map.open_stream(1)?;
599        map.push_trailing_metadata(1, headers("grpc-status", b"12"))?;
600        map.mark_remote_end_stream(1)?;
601        assert_eq!(map.pop_initial_metadata(1)?, None);
602        assert_eq!(map.pop_message(1)?, None);
603        assert_eq!(map.take_trailers(1)?, Some(headers("grpc-status", b"12")));
604        open(&mut map, 3)?;
605        map.pop_initial_metadata(3)?;
606        assert!(map.push_initial_metadata(3, vec![]).is_err());
607        map.push_trailing_metadata(3, vec![])?;
608        map.take_trailers(3)?;
609        assert!(map.push_trailing_metadata(3, vec![]).is_err());
610        Ok(())
611    }
612    #[test]
613    fn goaway_prunes_and_enforces_decreasing_boundary() -> SlotResult<()> {
614        let mut map = StreamSlotMap::default();
615        for id in [1, 3, 5, 7, i32::MAX] {
616            open(&mut map, id)?;
617        }
618        assert!(map.open_stream(1).is_err());
619        map.push_message(1, message(b"retained"))?;
620        assert!(map.apply_goaway(i32::MAX, 0)?.is_empty());
621        assert!(matches!(
622            map.open_stream(9),
623            Err(SlotMapError::GoAway { .. })
624        ));
625        open(&mut map, 8)?;
626        assert_eq!(map.apply_goaway(3, 0)?, vec![5, 7, i32::MAX]);
627        assert!(map.open_stream(5).is_err());
628        assert_eq!(map.pop_message(1)?, Some(message(b"retained")));
629        assert_eq!(map.apply_goaway(1, 0)?, vec![3]);
630        assert!(map.apply_goaway(7, 0).is_err());
631        assert!(map.apply_goaway(-1, 0).is_err());
632        assert_eq!(map.apply_goaway(0, 7)?, vec![1]);
633        assert!(map.get(8).is_some());
634        let mut server = StreamSlotMap::with_role(StreamLimits::default(), EndpointRole::Server);
635        for id in [1, 2, 3, 4] {
636            open(&mut server, id)?;
637        }
638        server.push_message(3, message(b"request"))?;
639        assert_eq!(server.apply_goaway(2, 0)?, vec![4]);
640        assert!(server.open_stream(6).is_err());
641        assert_eq!(server.apply_goaway(0, 0)?, vec![2]);
642        assert_eq!(server.pop_message(3)?, Some(message(b"request")));
643        open(&mut server, 5)?;
644        assert!(matches!(
645            map.open_stream(1),
646            Err(SlotMapError::GoAway { error_code: 7, .. })
647        ));
648        Ok(())
649    }
650    #[test]
651    fn completed_response_survives_no_error_reset() -> SlotResult<()> {
652        for (code, complete) in [(0, true), (0, false), (8, true)] {
653            let mut map = StreamSlotMap::default();
654            open(&mut map, 1)?;
655            map.push_message(1, message(b"early response"))?;
656            if complete {
657                map.push_trailing_metadata(1, headers("grpc-status", b"0"))?;
658                map.mark_remote_end_stream(1)?;
659            } else {
660                map.append_recv_bytes(1, &[0, 0])?;
661            }
662            let bytes = map.get(1).expect("slot").buffered_bytes();
663            map.record_rst_stream(1, code)?;
664            assert!(map.record_rst_stream(1, code).is_err());
665            let slot = map.get(1).expect("slot");
666            assert!(slot.remote_end_stream() && slot.local_end_stream());
667            if code == 0 && complete {
668                assert_eq!(slot.buffered_bytes(), bytes);
669                assert_eq!(map.pop_initial_metadata(1)?, Some(vec![]));
670                assert_eq!(map.pop_message(1)?, Some(message(b"early response")));
671                assert_eq!(map.take_trailers(1)?, Some(headers("grpc-status", b"0")));
672                assert_eq!(map.get(1).expect("slot").buffered_bytes(), 0);
673            } else {
674                assert_eq!(slot.buffered_bytes(), 0);
675                assert!(map.pop_initial_metadata(1).is_err());
676                assert!(map.take_trailers(1).is_err());
677                assert!(matches!(
678                    map.pop_message(1),
679                    Err(SlotMapError::Reset { .. })
680                ));
681                assert!(map.push_message(1, message(b"late")).is_err());
682            }
683            map.remove_closed_stream(1)?;
684        }
685        Ok(())
686    }
687    #[test]
688    fn fragmented_frames_compression_and_invalid_prefixes() -> SlotResult<()> {
689        let mut map = StreamSlotMap::default();
690        open(&mut map, 1)?;
691        let mut wire = frame(b"abc");
692        wire.extend(frame(b""));
693        for byte in wire {
694            map.append_recv_bytes(1, &[byte])?;
695        }
696        for payload in [b"abc".as_slice(), b"".as_slice()] {
697            assert_eq!(map.pop_message(1)?, Some(message(payload)));
698        }
699        let mut compressed = frame(b"xyz");
700        compressed[0] = 1;
701        map.append_recv_bytes(1, &compressed)?;
702        assert!(map.pop_message(1)?.expect("message").compressed);
703        assert!(map.append_recv_bytes(1, &[2, 0, 0, 0, 0]).is_err());
704        assert!(map.remove_closed_stream(1).is_err());
705        map.mark_local_end_stream(1)?;
706        map.push_message(1, message(b"half-open"))?;
707        assert!(map.mark_local_end_stream(1).is_err());
708        map.mark_remote_end_stream(1)?;
709        assert!(map.push_message(1, message(b"late")).is_err());
710        map.remove_closed_stream(1)?;
711        Ok(())
712    }
713    #[test]
714    fn rejected_batches_preserve_partial_frames() -> SlotResult<()> {
715        let mut map = bounded(128, 1);
716        open(&mut map, 1)?;
717        let wire = frame(b"ok");
718        map.append_recv_bytes(1, &wire[..2])?;
719        let mut queue_excess = wire[2..].to_vec();
720        queue_excess.extend(frame(b""));
721        let mut malformed = wire[2..].to_vec();
722        malformed.push(2);
723        for rejected in [queue_excess, malformed, vec![0; 128]] {
724            assert!(map.append_recv_bytes(1, &rejected).is_err());
725            let slot = map.get(1).expect("slot");
726            assert_eq!(slot.buffered_bytes(), 2);
727            assert_eq!(slot.queued_message_count(), 0);
728        }
729        map.append_recv_bytes(1, &wire[2..])?;
730        assert_eq!(map.pop_message(1)?, Some(message(b"ok")));
731        assert_eq!(map.get(1).expect("slot").buffered_bytes(), 0);
732        Ok(())
733    }
734    #[test]
735    fn truncated_frames_and_declared_lengths_are_bounded() -> SlotResult<()> {
736        for wire in [&[0, 0][..], &[0, 0, 0, 0, 3, b'a'][..]] {
737            let mut map = bounded(128, 4);
738            open(&mut map, 1)?;
739            map.append_recv_bytes(1, wire)?;
740            assert!(map.mark_remote_end_stream(1).is_err());
741        }
742        let mut map = bounded(8, 4);
743        open(&mut map, 1)?;
744        let oversized = [0, 0xff, 0xff, 0xff, 0xff];
745        assert!(map.append_recv_bytes(1, &oversized).is_err());
746        map.append_recv_bytes(1, &[0, 0, 0])?;
747        assert_eq!(map.get(1).expect("slot").buffered_bytes(), 3);
748        assert!(map.push_message(1, message(b"123456")).is_err());
749        Ok(())
750    }
751    #[test]
752    fn byte_and_queue_budgets_release_on_consumption() -> SlotResult<()> {
753        let mut map = bounded(128, 1);
754        map.open_stream(1)?;
755        map.push_initial_metadata(1, headers("key", b"value"))?;
756        assert!(map.push_message(1, message(&[0; 128])).is_err());
757        map.pop_initial_metadata(1)?;
758        assert_eq!(map.get(1).expect("slot").buffered_bytes(), 0);
759        map.push_message(1, message(&[0; 64]))?;
760        assert!(map.push_trailing_metadata(1, headers("x", b"y")).is_err());
761        map.pop_message(1)?;
762        map.push_message(1, message(b""))?;
763        assert!(map.push_message(1, message(b"")).is_err());
764        assert_eq!(map.get(1).expect("slot").queued_message_count(), 1);
765        assert!(map.append_recv_bytes(1, &frame(b"blocked")).is_err());
766        assert_eq!(map.pop_message(1)?, Some(message(b"")));
767        map.append_recv_bytes(1, &frame(b"next"))?;
768        assert_eq!(map.pop_message(1)?, Some(message(b"next")));
769        map.push_trailing_metadata(1, headers("x", b"y"))?;
770        map.take_trailers(1)?;
771        assert_eq!(map.get(1).expect("slot").buffered_bytes(), 0);
772        Ok(())
773    }
774}