h3i/
lib.rs

1// Copyright (C) 2024, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27//! h3i - low-level HTTP/3 debug and testing
28//!
29//! HTTP/3 ([RFC 9114]) is the wire format for HTTP semantics ([RFC 9110]). The
30//! RFCs contain a range of requirements about how Request or Response messages
31//! are generated, serialized, sent, received, parsed, and consumed. QUIC ([RFC
32//! 9000]) streams are used for these messages along with other control and
33//! QPACK ([RFC 9204]) header compression instructions.
34//!
35//! h3i provides a highly configurable HTTP/3 client that can bend RFC rules in
36//! order to test the behavior of servers. QUIC streams can be opened, fin'd,
37//! stopped or reset at any point in time. HTTP/3 frames can be sent on any
38//! stream, in any order, containing user-controlled content (both legal and
39//! illegal).
40//!
41//! # Example
42//!
43//! The following example sends a request with its Content-Length header set to
44//! 5, but with its body only consisting of 4 bytes. This is classified as a
45//! [malformed request], and the server should respond with a 400 Bad Request
46//! response. Once h3i receives the response, it will close the connection.
47//!
48//! ```no_run
49//! use h3i::actions::h3::Action;
50//! use h3i::actions::h3::StreamEvent;
51//! use h3i::actions::h3::StreamEventType;
52//! use h3i::actions::h3::WaitType;
53//! use h3i::client::sync_client;
54//! use h3i::config::Config;
55//! use quiche::h3::frame::Frame;
56//! use quiche::h3::Header;
57//! use quiche::h3::NameValue;
58//!
59//! fn main() {
60//!    /// The QUIC stream to send the frames on. See
61//!    /// https://datatracker.ietf.org/doc/html/rfc9000#name-streams and
62//!    /// https://datatracker.ietf.org/doc/html/rfc9114#request-streams for more.
63//!    const STREAM_ID: u64 = 0;
64//!
65//!    let config = Config::new()
66//!        .with_host_port("blog.cloudflare.com".to_string())
67//!        .with_idle_timeout(2000)
68//!        .build()
69//!        .unwrap();
70//!
71//!    let headers = vec![
72//!        Header::new(b":method", b"POST"),
73//!        Header::new(b":scheme", b"https"),
74//!        Header::new(b":authority", b"blog.cloudflare.com"),
75//!        Header::new(b":path", b"/"),
76//!        // We say that we're going to send a body with 5 bytes...
77//!        Header::new(b"content-length", b"5"),
78//!    ];
79//!
80//!    let header_block = encode_header_block(&headers).unwrap();
81//!
82//!    let actions = vec![
83//!        Action::SendHeadersFrame {
84//!            stream_id: STREAM_ID,
85//!            fin_stream: false,
86//!            headers,
87//!            frame: Frame::Headers { header_block },
88//!            literal_headers: false,
89//!        },
90//!        Action::SendFrame {
91//!            stream_id: STREAM_ID,
92//!            fin_stream: true,
93//!            frame: Frame::Data {
94//!                // ...but, in actuality, we only send 4 bytes. This should yield a
95//!                // 400 Bad Request response from an RFC-compliant
96//!                // server: https://datatracker.ietf.org/doc/html/rfc9114#section-4.1.2-3
97//!                payload: b"test".to_vec(),
98//!            },
99//!        },
100//!        Action::Wait {
101//!            wait_type: WaitType::StreamEvent(StreamEvent {
102//!                stream_id: STREAM_ID,
103//!                event_type: StreamEventType::Headers,
104//!            }),
105//!        },
106//!        Action::ConnectionClose {
107//!            error: quiche::ConnectionError {
108//!                is_app: true,
109//!                error_code: quiche::h3::WireErrorCode::NoError as u64,
110//!                reason: vec![],
111//!            },
112//!        },
113//!    ];
114//!
115//!    // This example doesn't use close trigger frames, since we manually close the connection upon
116//!    // receiving a HEADERS frame on stream 0.
117//!    let close_trigger_frames = None;
118//!    let summary = sync_client::connect(config, actions, close_trigger_frames);
119//!
120//!    println!(
121//!        "=== received connection summary! ===\n\n{}",
122//!        serde_json::to_string_pretty(&summary).unwrap_or_else(|e| e.to_string())
123//!    );
124//! }
125//!
126//! // SendHeadersFrame requires a QPACK-encoded header block. h3i provides a
127//! // `send_headers_frame` helper function to abstract this, but for clarity, we do
128//! // it here.
129//! fn encode_header_block(
130//!     headers: &[quiche::h3::Header],
131//! ) -> std::result::Result<Vec<u8>, String> {
132//!     let mut encoder = quiche::h3::qpack::Encoder::new();
133//!
134//!     let headers_len = headers
135//!         .iter()
136//!         .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);
137//!
138//!     let mut header_block = vec![0; headers_len];
139//!     let len = encoder
140//!         .encode(headers, &mut header_block)
141//!         .map_err(|_| "Internal Error")?;
142//!
143//!     header_block.truncate(len);
144//!
145//!     Ok(header_block)
146//! }
147//! ```
148
149//! [RFC 9000]: https://www.rfc-editor.org/rfc/rfc9000.html
150//! [RFC 9110]: https://www.rfc-editor.org/rfc/rfc9110.html
151//! [RFC 9114]: https://www.rfc-editor.org/rfc/rfc9114.html
152//! [RFC 9204]: https://www.rfc-editor.org/rfc/rfc9204.html
153//! [malformed request]: https://datatracker.ietf.org/doc/html/rfc9114#section-4.1.2-3
154
155use qlog::events::quic::PacketHeader;
156use qlog::events::quic::PacketSent;
157use qlog::events::quic::PacketType;
158use qlog::events::quic::QuicFrame;
159use qlog::events::EventData;
160use quiche::h3::qpack::encode_int;
161use quiche::h3::qpack::encode_str;
162use quiche::h3::qpack::LITERAL;
163use quiche::h3::NameValue;
164use smallvec::SmallVec;
165
166#[cfg(not(feature = "async"))]
167pub use quiche;
168#[cfg(feature = "async")]
169pub use tokio_quiche::quiche;
170
171/// The ID for an HTTP/3 control stream type.
172///
173/// See <https://datatracker.ietf.org/doc/html/rfc9114#name-control-streams>.
174pub const HTTP3_CONTROL_STREAM_TYPE_ID: u64 = 0x0;
175
176/// The ID for an HTTP/3 push stream type.
177///
178/// See <https://datatracker.ietf.org/doc/html/rfc9114#name-push-streams>.
179pub const HTTP3_PUSH_STREAM_TYPE_ID: u64 = 0x1;
180
181/// The ID for a QPACK encoder stream type.
182///
183/// See <https://datatracker.ietf.org/doc/html/rfc9204#section-4.2-2.1>.
184pub const QPACK_ENCODER_STREAM_TYPE_ID: u64 = 0x2;
185
186/// The ID for a QPACK decoder stream type.
187///
188/// See <https://datatracker.ietf.org/doc/html/rfc9204#section-4.2-2.2>.
189pub const QPACK_DECODER_STREAM_TYPE_ID: u64 = 0x3;
190
191#[derive(Default)]
192struct StreamIdAllocator {
193    id: u64,
194}
195
196impl StreamIdAllocator {
197    pub fn take_next_id(&mut self) -> u64 {
198        let old = self.id;
199        self.id += 4;
200
201        old
202    }
203
204    pub fn peek_next_id(&mut self) -> u64 {
205        self.id
206    }
207}
208
209/// Encodes a header block literally. Unlike [`encode_header_block`],
210/// this function encodes all the headers exactly as provided. This
211/// means it does not use the huffman lookup table, nor does it convert
212/// the header names to lowercase before encoding.
213fn encode_header_block_literal(
214    headers: &[quiche::h3::Header],
215) -> std::result::Result<Vec<u8>, String> {
216    // This is a combination of a modified `quiche::h3::qpack::Encoder::encode`
217    // and the [`encode_header_block`] function.
218    let headers_len = headers
219        .iter()
220        .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);
221
222    let mut header_block = vec![0; headers_len];
223
224    let mut b = octets::OctetsMut::with_slice(&mut header_block);
225
226    // Required Insert Count.
227    encode_int(0, 0, 8, &mut b).map_err(|e| format!("{e:?}"))?;
228
229    // Base.
230    encode_int(0, 0, 7, &mut b).map_err(|e| format!("{e:?}"))?;
231
232    for h in headers {
233        encode_str::<false>(h.name(), LITERAL, 3, &mut b)
234            .map_err(|e| format!("{e:?}"))?;
235        encode_str::<false>(h.value(), 0, 7, &mut b)
236            .map_err(|e| format!("{e:?}"))?;
237    }
238
239    let len = b.off();
240
241    header_block.truncate(len);
242    Ok(header_block)
243}
244
245fn encode_header_block(
246    headers: &[quiche::h3::Header],
247) -> std::result::Result<Vec<u8>, String> {
248    let mut encoder = quiche::h3::qpack::Encoder::new();
249
250    let headers_len = headers
251        .iter()
252        .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);
253
254    let mut header_block = vec![0; headers_len];
255    let len = encoder
256        .encode(headers, &mut header_block)
257        .map_err(|_| "Internal Error")?;
258
259    header_block.truncate(len);
260
261    Ok(header_block)
262}
263
264fn fake_packet_header() -> PacketHeader {
265    PacketHeader {
266        packet_type: PacketType::OneRtt,
267        packet_number: None,
268        flags: None,
269        token: None,
270        length: None,
271        version: None,
272        scil: None,
273        dcil: None,
274        scid: None,
275        dcid: None,
276    }
277}
278
279fn fake_packet_sent(frames: Option<SmallVec<[QuicFrame; 1]>>) -> EventData {
280    EventData::PacketSent(PacketSent {
281        header: fake_packet_header(),
282        frames,
283        ..Default::default()
284    })
285}
286
287pub mod actions;
288pub mod client;
289pub mod config;
290pub mod frame;
291pub mod frame_parser;
292pub mod prompts;
293pub mod recordreplay;