Skip to main content

micro_h2/
conn.rs

1//! A client connection: one HPACK context, a few streams, and flow control.
2//!
3//! # Flow control is the part that bites
4//!
5//! An HTTP/2 receiver advertises how much it is willing to accept, and a sender
6//! that has used up that allowance simply stops. Both windows start at 65535
7//! bytes — one for the connection, one for each stream — and neither grows
8//! unless the receiver says so with `WINDOW_UPDATE`.
9//!
10//! For a client that fetches small responses this never comes up, which is
11//! exactly why it is dangerous: the netmap long-poll streams megabytes, and a
12//! client that never sends `WINDOW_UPDATE` receives precisely 65535 bytes and
13//! then hangs forever. It looks like the server stopped talking. It did, because
14//! we told it to.
15//!
16//! So this connection raises the connection window immediately after the
17//! preface, advertises a large per-stream window in its `SETTINGS`, and returns
18//! a `WINDOW_UPDATE` for every byte of `DATA` it consumes.
19//!
20//! # Sans-io
21//!
22//! The caller reads [`frame::HEADER_LEN`] bytes, learns the payload length,
23//! reads that, and hands the whole frame to [`Connection::recv`]. Anything that
24//! must be sent in reply is written to the caller's buffer.
25
26use crate::frame::{self, FrameHeader, FrameType, flags, settings};
27use crate::hpack::{self, Decoder};
28use crate::{Error, hpack::encode};
29
30/// How many requests may be in flight at once. Registration and the map
31/// long-poll, with one spare.
32pub const MAX_STREAMS: usize = 4;
33
34/// The largest header block this client will reassemble across `CONTINUATION`
35/// frames.
36pub const MAX_HEADER_BLOCK: usize = 4096;
37
38/// What we advertise as our per-stream receive window, and what we top the
39/// connection window up to.
40///
41/// Large enough that a streaming response is never throttled by the round trip
42/// of a `WINDOW_UPDATE`, and it costs nothing: the window bounds what the peer
43/// may have in flight, not what we must buffer, because every `DATA` frame is
44/// handed to the caller as it arrives.
45pub const RECEIVE_WINDOW: u32 = 1 << 20;
46
47/// The default both windows start at, before anything is negotiated.
48const DEFAULT_WINDOW: u32 = 65_535;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51struct Stream {
52    id: u32,
53    /// Bytes received since the last `WINDOW_UPDATE` for this stream.
54    consumed: u32,
55    open: bool,
56}
57
58/// What arrived.
59#[derive(Debug, PartialEq, Eq)]
60pub enum Event<'a> {
61    /// Nothing the caller needs to act on: a settings exchange, a ping, an
62    /// unknown frame type. Anything that needed a reply is already in `out`.
63    Nothing,
64    /// A complete header block. The fields were passed to the callback.
65    Headers { stream: u32, end_stream: bool },
66    /// Response body bytes. Borrowed from the caller's own frame buffer, so
67    /// nothing is copied.
68    Data {
69        stream: u32,
70        data: &'a [u8],
71        end_stream: bool,
72    },
73    /// The peer reset one stream. The connection is still usable.
74    Reset { stream: u32, code: u32 },
75    /// The peer is shutting the connection down.
76    GoAway { code: u32 },
77}
78
79pub struct Connection {
80    decoder: Decoder,
81    streams: heapless::Vec<Stream, MAX_STREAMS>,
82    next_stream: u32,
83    /// Reassembly buffer for a header block split across `CONTINUATION` frames.
84    header_block: heapless::Vec<u8, MAX_HEADER_BLOCK>,
85    /// Which stream the block being reassembled belongs to.
86    header_stream: u32,
87    header_end_stream: bool,
88    /// Bytes received on the connection since the last `WINDOW_UPDATE`.
89    consumed: u32,
90    /// What the peer said it will accept in one frame.
91    peer_max_frame: usize,
92}
93
94impl Default for Connection {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100impl Connection {
101    pub fn new() -> Self {
102        Self {
103            decoder: Decoder::new(hpack::DEFAULT_TABLE_SIZE),
104            streams: heapless::Vec::new(),
105            // Client-initiated streams are odd-numbered.
106            next_stream: 1,
107            header_block: heapless::Vec::new(),
108            header_stream: 0,
109            header_end_stream: false,
110            consumed: 0,
111            peer_max_frame: frame::DEFAULT_MAX_FRAME,
112        }
113    }
114
115    /// Write the client preface, our settings, and the connection-level window
116    /// update that stops a large response stalling at 65535 bytes.
117    pub fn start(&mut self, out: &mut [u8]) -> Result<usize, Error> {
118        let preface = frame::CLIENT_PREFACE;
119        out.get_mut(..preface.len())
120            .ok_or(Error::BufferTooSmall)?
121            .copy_from_slice(preface);
122        let mut len = preface.len();
123
124        // Push is refused outright rather than handled: a client that never
125        // accepts a promise needs no state for one.
126        let mut payload = [0u8; 18];
127        write_setting(&mut payload[0..6], settings::ENABLE_PUSH, 0);
128        write_setting(
129            &mut payload[6..12],
130            settings::INITIAL_WINDOW_SIZE,
131            RECEIVE_WINDOW,
132        );
133        write_setting(
134            &mut payload[12..18],
135            settings::MAX_CONCURRENT_STREAMS,
136            MAX_STREAMS as u32,
137        );
138        len += frame::write_frame(FrameType::Settings, 0, 0, &payload, &mut out[len..])?;
139
140        // SETTINGS_INITIAL_WINDOW_SIZE applies to streams only; the connection
141        // window can be raised only by an explicit update.
142        let increment = (RECEIVE_WINDOW - DEFAULT_WINDOW).to_be_bytes();
143        len += frame::write_frame(FrameType::WindowUpdate, 0, 0, &increment, &mut out[len..])?;
144        Ok(len)
145    }
146
147    /// Open a stream and send a request, headers and body together.
148    ///
149    /// Returns the stream identifier and how many bytes were written.
150    #[allow(clippy::too_many_arguments)]
151    pub fn request(
152        &mut self,
153        method: &str,
154        path: &str,
155        authority: &str,
156        scheme: &str,
157        extra: &[(&str, &str)],
158        body: &[u8],
159        out: &mut [u8],
160    ) -> Result<(u32, usize), Error> {
161        let id = self.next_stream;
162        self.streams
163            .push(Stream {
164                id,
165                consumed: 0,
166                open: true,
167            })
168            .map_err(|_| Error::TooManyStreams)?;
169        self.next_stream += 2;
170
171        // Pseudo-headers must come first and in this order; a server is entitled
172        // to reject a block that interleaves them with ordinary fields.
173        let mut block = [0u8; MAX_HEADER_BLOCK];
174        let mut block_len = 0;
175        block_len = encode::encode_header(":method", method, &mut block, block_len)?;
176        block_len = encode::encode_header(":path", path, &mut block, block_len)?;
177        block_len = encode::encode_header(":scheme", scheme, &mut block, block_len)?;
178        block_len = encode::encode_header(":authority", authority, &mut block, block_len)?;
179        for (name, value) in extra {
180            block_len = encode::encode_header(name, value, &mut block, block_len)?;
181        }
182
183        // No CONTINUATION is emitted: a header block that does not fit one frame
184        // would need one, and these two request shapes never come close.
185        if block_len > self.peer_max_frame {
186            return Err(Error::FrameTooLarge);
187        }
188
189        let end_stream = if body.is_empty() {
190            flags::END_STREAM
191        } else {
192            0
193        };
194        let mut len = frame::write_frame(
195            FrameType::Headers,
196            flags::END_HEADERS | end_stream,
197            id,
198            &block[..block_len],
199            out,
200        )?;
201
202        if !body.is_empty() {
203            if body.len() > self.peer_max_frame {
204                return Err(Error::FrameTooLarge);
205            }
206            len += frame::write_frame(
207                FrameType::Data,
208                flags::END_STREAM,
209                id,
210                body,
211                &mut out[len..],
212            )?;
213        }
214        Ok((id, len))
215    }
216
217    /// Consume one whole frame.
218    ///
219    /// `frame` is the nine-byte header followed by exactly its payload. Replies
220    /// this connection owes — settings and ping acknowledgements, window updates
221    /// — are written to `out`, and the caller must send them.
222    pub fn recv<'a>(
223        &mut self,
224        bytes: &'a [u8],
225        mut on_header: impl FnMut(&str, &str),
226        out: &mut [u8],
227    ) -> Result<(Event<'a>, usize), Error> {
228        let header = FrameHeader::parse(bytes)?;
229        let payload = bytes
230            .get(frame::HEADER_LEN..frame::HEADER_LEN + header.length)
231            .ok_or(Error::Incomplete)?;
232        let mut written = 0;
233
234        match header.kind {
235            FrameType::Settings => {
236                if !header.has(flags::ACK) {
237                    self.apply_settings(payload)?;
238                    // An unacknowledged SETTINGS blocks the peer indefinitely.
239                    written = frame::write_frame(FrameType::Settings, flags::ACK, 0, &[], out)?;
240                }
241                Ok((Event::Nothing, written))
242            }
243
244            FrameType::Ping => {
245                if !header.has(flags::ACK) {
246                    // The payload must be echoed exactly.
247                    written = frame::write_frame(FrameType::Ping, flags::ACK, 0, payload, out)?;
248                }
249                Ok((Event::Nothing, written))
250            }
251
252            FrameType::Headers | FrameType::Continuation => {
253                let block = if header.kind == FrameType::Headers {
254                    self.header_block.clear();
255                    self.header_stream = header.stream;
256                    self.header_end_stream = header.has(flags::END_STREAM);
257                    let body = frame::strip_padding(payload, header.flags)?;
258                    // A priority block sits between the padding and the header
259                    // block, and feeding it to HPACK fails on a valid frame.
260                    if header.has(flags::PRIORITY) {
261                        body.get(5..).ok_or(Error::Protocol)?
262                    } else {
263                        body
264                    }
265                } else {
266                    payload
267                };
268
269                self.header_block
270                    .extend_from_slice(block)
271                    .map_err(|_| Error::BufferTooSmall)?;
272
273                if !header.has(flags::END_HEADERS) {
274                    // More CONTINUATION frames to come. Decoding a partial block
275                    // would corrupt the HPACK state for the whole connection.
276                    return Ok((Event::Nothing, 0));
277                }
278
279                let stream = self.header_stream;
280                let end_stream = self.header_end_stream;
281                self.decoder.decode(&self.header_block, &mut on_header)?;
282                if end_stream {
283                    self.close(stream);
284                }
285                Ok((Event::Headers { stream, end_stream }, 0))
286            }
287
288            FrameType::Data => {
289                let data = frame::strip_padding(payload, header.flags)?;
290                // The window is consumed by the whole payload, padding included,
291                // not just the bytes we hand back.
292                written = self.credit(header.stream, header.length as u32, out)?;
293                let end_stream = header.has(flags::END_STREAM);
294                if end_stream {
295                    self.close(header.stream);
296                }
297                Ok((
298                    Event::Data {
299                        stream: header.stream,
300                        data,
301                        end_stream,
302                    },
303                    written,
304                ))
305            }
306
307            FrameType::RstStream => {
308                let code = read_u32(payload).ok_or(Error::Protocol)?;
309                self.close(header.stream);
310                Ok((
311                    Event::Reset {
312                        stream: header.stream,
313                        code,
314                    },
315                    0,
316                ))
317            }
318
319            FrameType::GoAway => {
320                // last-stream-id, then the error code.
321                let code = payload
322                    .get(4..8)
323                    .and_then(read_u32)
324                    .ok_or(Error::Protocol)?;
325                Ok((Event::GoAway { code }, 0))
326            }
327
328            // Window updates from the peer govern what *we* may send. Our
329            // requests are a few kilobytes against a 65535-byte default, so
330            // there is nothing to track; a client that streamed large bodies
331            // would have to.
332            FrameType::WindowUpdate | FrameType::Priority => Ok((Event::Nothing, 0)),
333
334            // Push was refused in our SETTINGS, so a promise is a protocol
335            // violation rather than something to ignore.
336            FrameType::PushPromise => Err(Error::Protocol),
337
338            // RFC 7540 requires unknown types to be discarded.
339            FrameType::Unknown(_) => Ok((Event::Nothing, 0)),
340        }
341    }
342
343    /// Return flow-control credit for `length` bytes consumed on `stream`.
344    fn credit(&mut self, stream: u32, length: u32, out: &mut [u8]) -> Result<usize, Error> {
345        if length == 0 {
346            return Ok(0);
347        }
348        let mut written = 0;
349
350        // Topping up only past a threshold keeps a stream of small DATA frames
351        // from producing one update each.
352        const THRESHOLD: u32 = RECEIVE_WINDOW / 2;
353
354        self.consumed += length;
355        if self.consumed >= THRESHOLD {
356            let increment = self.consumed.to_be_bytes();
357            written += frame::write_frame(FrameType::WindowUpdate, 0, 0, &increment, out)?;
358            self.consumed = 0;
359        }
360
361        if let Some(entry) = self.streams.iter_mut().find(|s| s.id == stream) {
362            entry.consumed += length;
363            if entry.consumed >= THRESHOLD {
364                let increment = entry.consumed.to_be_bytes();
365                written += frame::write_frame(
366                    FrameType::WindowUpdate,
367                    0,
368                    stream,
369                    &increment,
370                    &mut out[written..],
371                )?;
372                entry.consumed = 0;
373            }
374        }
375        Ok(written)
376    }
377
378    fn apply_settings(&mut self, payload: &[u8]) -> Result<(), Error> {
379        if !payload.len().is_multiple_of(6) {
380            return Err(Error::Protocol);
381        }
382        for entry in payload.chunks_exact(6) {
383            let identifier = u16::from_be_bytes([entry[0], entry[1]]);
384            let value = u32::from_be_bytes([entry[2], entry[3], entry[4], entry[5]]);
385            match identifier {
386                settings::MAX_FRAME_SIZE => self.peer_max_frame = value as usize,
387                // The peer is telling us how large a dynamic table it will use
388                // when encoding, so our decoder must be willing to hold that
389                // much or indices will not resolve.
390                settings::HEADER_TABLE_SIZE => {
391                    self.decoder = Decoder::new(value as usize);
392                }
393                // Everything else governs what we may send, and our requests are
394                // too small for any of it to bind.
395                _ => {}
396            }
397        }
398        Ok(())
399    }
400
401    fn close(&mut self, stream: u32) {
402        if let Some(entry) = self.streams.iter_mut().find(|s| s.id == stream) {
403            entry.open = false;
404        }
405        self.streams.retain(|s| s.open);
406    }
407
408    pub fn open_streams(&self) -> usize {
409        self.streams.len()
410    }
411}
412
413fn write_setting(out: &mut [u8], identifier: u16, value: u32) {
414    out[0..2].copy_from_slice(&identifier.to_be_bytes());
415    out[2..6].copy_from_slice(&value.to_be_bytes());
416}
417
418fn read_u32(bytes: &[u8]) -> Option<u32> {
419    Some(u32::from_be_bytes(bytes.get(..4)?.try_into().ok()?))
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425
426    /// Build a frame the way a server would, for feeding to `recv`.
427    fn frame_bytes(
428        kind: FrameType,
429        flags: u8,
430        stream: u32,
431        payload: &[u8],
432    ) -> heapless::Vec<u8, 512> {
433        let mut out = heapless::Vec::<u8, 512>::new();
434        out.resize_default(512).unwrap();
435        let len = frame::write_frame(kind, flags, stream, payload, &mut out).unwrap();
436        out.truncate(len);
437        out
438    }
439
440    #[test]
441    fn the_preface_is_exactly_what_the_rfc_requires() {
442        let mut connection = Connection::new();
443        let mut out = [0u8; 128];
444        let len = connection.start(&mut out).unwrap();
445        assert!(out[..len].starts_with(b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"));
446
447        // Then SETTINGS, then the connection-level window update. Without the
448        // last one a streaming response stops dead at 65535 bytes.
449        let after = &out[frame::CLIENT_PREFACE.len()..len];
450        let settings = FrameHeader::parse(after).unwrap();
451        assert_eq!(settings.kind, FrameType::Settings);
452        assert_eq!(settings.stream, 0);
453
454        let update = &after[frame::HEADER_LEN + settings.length..];
455        let update_header = FrameHeader::parse(update).unwrap();
456        assert_eq!(update_header.kind, FrameType::WindowUpdate);
457        assert_eq!(update_header.stream, 0);
458        let increment = read_u32(&update[frame::HEADER_LEN..]).unwrap();
459        assert_eq!(increment, RECEIVE_WINDOW - DEFAULT_WINDOW);
460    }
461
462    #[test]
463    fn a_request_opens_an_odd_numbered_stream_and_ends_it() {
464        let mut connection = Connection::new();
465        let mut out = [0u8; 512];
466        let (stream, len) = connection
467            .request(
468                "POST",
469                "/machine/register",
470                "127.0.0.1:8080",
471                "http",
472                &[("content-type", "application/json")],
473                b"{}",
474                &mut out,
475            )
476            .unwrap();
477
478        // Client streams are odd, and the first is 1.
479        assert_eq!(stream, 1);
480        let headers = FrameHeader::parse(&out).unwrap();
481        assert_eq!(headers.kind, FrameType::Headers);
482        assert!(headers.has(flags::END_HEADERS));
483        assert!(!headers.has(flags::END_STREAM), "a body follows");
484
485        let data = FrameHeader::parse(&out[frame::HEADER_LEN + headers.length..]).unwrap();
486        assert_eq!(data.kind, FrameType::Data);
487        assert!(data.has(flags::END_STREAM));
488        assert_eq!(len, frame::HEADER_LEN * 2 + headers.length + data.length);
489
490        // The next request must not reuse the identifier.
491        let (second, _) = connection
492            .request("GET", "/", "h", "http", &[], b"", &mut out)
493            .unwrap();
494        assert_eq!(second, 3);
495    }
496
497    #[test]
498    fn settings_are_acknowledged_and_an_ack_is_not() {
499        let mut connection = Connection::new();
500        let mut out = [0u8; 128];
501
502        let settings = frame_bytes(FrameType::Settings, 0, 0, &[0, 5, 0, 0, 0x40, 0]);
503        let (event, written) = connection.recv(&settings, |_, _| {}, &mut out).unwrap();
504        assert_eq!(event, Event::Nothing);
505        let ack = FrameHeader::parse(&out[..written]).unwrap();
506        assert_eq!(ack.kind, FrameType::Settings);
507        assert!(ack.has(flags::ACK));
508        assert_eq!(ack.length, 0);
509
510        // Acknowledging an acknowledgement would loop forever.
511        let their_ack = frame_bytes(FrameType::Settings, flags::ACK, 0, &[]);
512        let (_, written) = connection.recv(&their_ack, |_, _| {}, &mut out).unwrap();
513        assert_eq!(written, 0);
514    }
515
516    #[test]
517    fn a_ping_is_echoed_exactly() {
518        let mut connection = Connection::new();
519        let mut out = [0u8; 128];
520        let payload = [1, 2, 3, 4, 5, 6, 7, 8];
521        let ping = frame_bytes(FrameType::Ping, 0, 0, &payload);
522        let (_, written) = connection.recv(&ping, |_, _| {}, &mut out).unwrap();
523        let header = FrameHeader::parse(&out[..written]).unwrap();
524        assert!(header.has(flags::ACK));
525        assert_eq!(&out[frame::HEADER_LEN..written], &payload);
526    }
527
528    #[test]
529    fn a_header_block_split_across_continuation_frames_is_reassembled() {
530        // Decoding either half alone would corrupt the HPACK table for the rest
531        // of the connection, so the partial frame must produce no event at all.
532        let mut connection = Connection::new();
533        let mut out = [0u8; 256];
534
535        // ":status: 200" then ":method: GET", as two indexed fields.
536        let first = frame_bytes(FrameType::Headers, 0, 1, &[0x88]);
537        let (event, _) = connection.recv(&first, |_, _| {}, &mut out).unwrap();
538        assert_eq!(
539            event,
540            Event::Nothing,
541            "an unterminated block yields nothing"
542        );
543
544        let mut seen = heapless::Vec::<(heapless::String<32>, heapless::String<32>), 4>::new();
545        let second = frame_bytes(FrameType::Continuation, flags::END_HEADERS, 1, &[0x82]);
546        let (event, _) = connection
547            .recv(
548                &second,
549                |name, value| {
550                    let mut n = heapless::String::new();
551                    let mut v = heapless::String::new();
552                    n.push_str(name).unwrap();
553                    v.push_str(value).unwrap();
554                    seen.push((n, v)).unwrap();
555                },
556                &mut out,
557            )
558            .unwrap();
559
560        assert!(matches!(event, Event::Headers { stream: 1, .. }));
561        assert_eq!(seen.len(), 2);
562        assert_eq!(seen[0].0.as_str(), ":status");
563        assert_eq!(seen[0].1.as_str(), "200");
564        assert_eq!(seen[1].0.as_str(), ":method");
565    }
566
567    #[test]
568    fn data_returns_the_payload_and_eventually_a_window_update() {
569        let mut connection = Connection::new();
570        let mut out = [0u8; 256];
571
572        let data = frame_bytes(FrameType::Data, 0, 1, b"hello");
573        let (event, written) = connection.recv(&data, |_, _| {}, &mut out).unwrap();
574        assert_eq!(
575            event,
576            Event::Data {
577                stream: 1,
578                data: b"hello",
579                end_stream: false
580            }
581        );
582        // Below the threshold, so no update yet: one update per small frame
583        // would be pure overhead.
584        assert_eq!(written, 0);
585    }
586
587    #[test]
588    fn a_long_response_gets_its_window_topped_up_before_it_can_stall() {
589        // The regression test for the failure mode that looks like a server
590        // fault: without this the peer stops after 65535 bytes.
591        let mut connection = Connection::new();
592        let mut out = [0u8; 256];
593        let payload = [0u8; 400];
594
595        let mut updates = 0;
596        let mut delivered = 0usize;
597        // Well past both the 65535-byte default window and our own top-up
598        // threshold, so a connection that never replenishes would have stalled
599        // long before the end of this loop.
600        let frames = 2_000;
601        for _ in 0..frames {
602            let data = frame_bytes(FrameType::Data, 0, 1, &payload);
603            let (event, written) = connection.recv(&data, |_, _| {}, &mut out).unwrap();
604            if let Event::Data { data, .. } = event {
605                delivered += data.len();
606            }
607            if written > 0 {
608                updates += 1;
609                let header = FrameHeader::parse(&out[..written]).unwrap();
610                assert_eq!(header.kind, FrameType::WindowUpdate);
611            }
612        }
613        assert_eq!(delivered, frames * 400);
614        assert!(
615            updates > 0,
616            "the connection window must be replenished, or the server stops at 65535 bytes"
617        );
618    }
619
620    #[test]
621    fn padding_and_priority_are_stripped_before_hpack_sees_the_block() {
622        let mut connection = Connection::new();
623        let mut out = [0u8; 256];
624        // pad length 2, priority (5 bytes), the block, then the padding.
625        let payload = [2, 0, 0, 0, 0, 0, 0x88, 0xaa, 0xbb];
626        let headers = frame_bytes(
627            FrameType::Headers,
628            flags::END_HEADERS | flags::PADDED | flags::PRIORITY,
629            1,
630            &payload,
631        );
632        let mut status = heapless::String::<8>::new();
633        connection
634            .recv(
635                &headers,
636                |name, value| {
637                    if name == ":status" {
638                        status.push_str(value).unwrap();
639                    }
640                },
641                &mut out,
642            )
643            .unwrap();
644        assert_eq!(status.as_str(), "200");
645    }
646
647    #[test]
648    fn goaway_and_reset_are_reported_rather_than_hidden() {
649        let mut connection = Connection::new();
650        let mut out = [0u8; 128];
651
652        let reset = frame_bytes(FrameType::RstStream, 0, 1, &[0, 0, 0, 8]);
653        let (event, _) = connection.recv(&reset, |_, _| {}, &mut out).unwrap();
654        assert_eq!(event, Event::Reset { stream: 1, code: 8 });
655
656        let goaway = frame_bytes(FrameType::GoAway, 0, 0, &[0, 0, 0, 1, 0, 0, 0, 2]);
657        let (event, _) = connection.recv(&goaway, |_, _| {}, &mut out).unwrap();
658        assert_eq!(event, Event::GoAway { code: 2 });
659    }
660
661    #[test]
662    fn a_promised_push_is_a_protocol_error_because_we_refused_push() {
663        let mut connection = Connection::new();
664        let mut out = [0u8; 128];
665        let promise = frame_bytes(FrameType::PushPromise, flags::END_HEADERS, 1, &[0, 0, 0, 2]);
666        assert_eq!(
667            connection.recv(&promise, |_, _| {}, &mut out).err(),
668            Some(Error::Protocol)
669        );
670    }
671
672    #[test]
673    fn an_unknown_frame_type_is_discarded_not_fatal() {
674        let mut connection = Connection::new();
675        let mut out = [0u8; 128];
676        let unknown = frame_bytes(FrameType::Unknown(0x63), 0, 0, b"whatever");
677        let (event, written) = connection.recv(&unknown, |_, _| {}, &mut out).unwrap();
678        assert_eq!(event, Event::Nothing);
679        assert_eq!(written, 0);
680    }
681}