micro_h2/lib.rs
1//! A minimal HTTP/2 client: `no_std`, allocation-free, sans-io.
2//!
3//! # Scope
4//!
5//! Enough HTTP/2 to be a client and no more. No server, no push, no priority,
6//! no trailers, and a small fixed number of concurrent streams. That is not
7//! laziness — HTTP/2's full surface is large, and every feature carried is
8//! another piece of state that has to be right. What this omits, it omits
9//! explicitly, and refuses rather than half-implements.
10//!
11//! # What is genuinely hard here
12//!
13//! Two things, and both fail silently rather than loudly:
14//!
15//! **HPACK's dynamic table is connection state built from the peer's stream.**
16//! Skipping an insertion does not lose a header; it shifts every subsequent
17//! index, so later headers decode as *different headers*. See [`hpack::dynamic`].
18//!
19//! **Flow control is not optional.** A receiver starts with a 65535-byte window
20//! per stream and per connection, and a sender that has exhausted it simply
21//! stops. A long-poll streaming a netmap will deliver exactly 65535 bytes and
22//! then hang, which looks like a server fault and is not. See [`conn`].
23//!
24//! # Sans-io
25//!
26//! [`conn::Connection`] never touches a socket. It is fed the bytes that arrived
27//! and writes the bytes to send, so the same code runs over TCP, over a ts2021
28//! Noise channel, and over a captured session in a test.
29//!
30//! ```
31//! let mut connection = micro_h2::Connection::new();
32//! let mut out = [0u8; 256];
33//! let written = connection.start(&mut out).unwrap();
34//! assert!(written > 24); // client preface plus SETTINGS and WINDOW_UPDATE
35//! ```
36
37#![no_std]
38#![forbid(unsafe_code)]
39
40pub mod conn;
41pub mod frame;
42mod headers;
43pub mod hpack;
44
45pub use conn::{Connection, Event};
46pub use frame::{FrameHeader, FrameType};
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum Error {
50 /// More bytes are needed before this can be decoded.
51 Incomplete,
52 /// The peer sent something that is not valid HTTP/2.
53 Protocol,
54 /// A header block could not be decoded. Fatal for the connection: HPACK
55 /// state cannot be resynchronised.
56 Hpack,
57 /// A fixed buffer was too small.
58 BufferTooSmall,
59 /// A frame larger than the negotiated maximum.
60 FrameTooLarge,
61 /// The peer closed the connection with GOAWAY.
62 GoAway,
63 /// The peer reset the stream.
64 StreamReset,
65 /// More concurrent streams than this client supports.
66 TooManyStreams,
67 /// The request body exceeds the peer's available flow-control credit.
68 /// Wait for WINDOW_UPDATE or a larger INITIAL_WINDOW_SIZE and retry.
69 FlowControl,
70}
71
72impl core::fmt::Display for Error {
73 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
74 f.write_str(match self {
75 Self::Incomplete => "incomplete",
76 Self::Protocol => "http/2 protocol error",
77 Self::Hpack => "header decoding failed",
78 Self::BufferTooSmall => "buffer too small",
79 Self::FrameTooLarge => "frame too large",
80 Self::GoAway => "the server sent GOAWAY",
81 Self::StreamReset => "the stream was reset",
82 Self::TooManyStreams => "too many concurrent streams",
83 Self::FlowControl => "request exceeds available flow-control credit",
84 })
85 }
86}
87
88impl core::error::Error for Error {}