h3i/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// Copyright (C) 2024, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

//! h3i - low-level HTTP/3 debug and testing
//!
//! HTTP/3 ([RFC 9114]) is the wire format for HTTP semantics ([RFC 9110]). The
//! RFCs contain a range of requirements about how Request or Response messages
//! are generated, serialized, sent, received, parsed, and consumed. QUIC ([RFC
//! 9000]) streams are used for these messages along with other control and
//! QPACK ([RFC 9204]) header compression instructions.
//!
//! h3i provides a highly configurable HTTP/3 client that can bend RFC rules in
//! order to test the behavior of servers. QUIC streams can be opened, fin'd,
//! stopped or reset at any point in time. HTTP/3 frames can be sent on any
//! stream, in any order, containing user-controlled content (both legal and
//! illegal).
//!
//! # Example
//!
//! The following example sends a request with its Content-Length header set to
//! 5, but with its body only consisting of 4 bytes. This is classified as a
//! [malformed request], and the server should respond with a 400 Bad Request
//! response. Once h3i receives the response, it will close the connection.
//!
//! ```no_run
//! use h3i::actions::h3::Action;
//! use h3i::actions::h3::StreamEvent;
//! use h3i::actions::h3::StreamEventType;
//! use h3i::actions::h3::WaitType;
//! use h3i::client::sync_client;
//! use h3i::config::Config;
//! use quiche::h3::frame::Frame;
//! use quiche::h3::Header;
//! use quiche::h3::NameValue;
//!
//! fn main() {
//!    /// The QUIC stream to send the frames on. See
//!    /// https://datatracker.ietf.org/doc/html/rfc9000#name-streams and
//!    /// https://datatracker.ietf.org/doc/html/rfc9114#request-streams for more.
//!    const STREAM_ID: u64 = 0;
//!
//!    let config = Config::new()
//!        .with_host_port("blog.cloudflare.com".to_string())
//!        .with_idle_timeout(2000)
//!        .build()
//!        .unwrap();
//!
//!    let headers = vec![
//!        Header::new(b":method", b"POST"),
//!        Header::new(b":scheme", b"https"),
//!        Header::new(b":authority", b"blog.cloudflare.com"),
//!        Header::new(b":path", b"/"),
//!        // We say that we're going to send a body with 5 bytes...
//!        Header::new(b"content-length", b"5"),
//!    ];
//!
//!    let header_block = encode_header_block(&headers).unwrap();
//!
//!    let actions = vec![
//!        Action::SendHeadersFrame {
//!            stream_id: STREAM_ID,
//!            fin_stream: false,
//!            headers,
//!            frame: Frame::Headers { header_block },
//!        },
//!        Action::SendFrame {
//!            stream_id: STREAM_ID,
//!            fin_stream: true,
//!            frame: Frame::Data {
//!                // ...but, in actuality, we only send 4 bytes. This should yield a
//!                // 400 Bad Request response from an RFC-compliant
//!                // server: https://datatracker.ietf.org/doc/html/rfc9114#section-4.1.2-3
//!                payload: b"test".to_vec(),
//!            },
//!        },
//!        Action::Wait {
//!            wait_type: WaitType::StreamEvent(StreamEvent {
//!                stream_id: STREAM_ID,
//!                event_type: StreamEventType::Headers,
//!            }),
//!        },
//!        Action::ConnectionClose {
//!            error: quiche::ConnectionError {
//!                is_app: true,
//!                error_code: quiche::h3::WireErrorCode::NoError as u64,
//!                reason: vec![],
//!            },
//!        },
//!    ];
//!
//!    let summary =
//!        sync_client::connect(config, &actions).expect("connection failed");
//!
//!    println!(
//!        "=== received connection summary! ===\n\n{}",
//!        serde_json::to_string_pretty(&summary).unwrap_or_else(|e| e.to_string())
//!    );
//! }
//!
//! // SendHeadersFrame requires a QPACK-encoded header block. h3i provides a
//! // `send_headers_frame` helper function to abstract this, but for clarity, we do
//! // it here.
//! fn encode_header_block(
//!     headers: &[quiche::h3::Header],
//! ) -> std::result::Result<Vec<u8>, String> {
//!     let mut encoder = quiche::h3::qpack::Encoder::new();
//!
//!     let headers_len = headers
//!         .iter()
//!         .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);
//!
//!     let mut header_block = vec![0; headers_len];
//!     let len = encoder
//!         .encode(headers, &mut header_block)
//!         .map_err(|_| "Internal Error")?;
//!
//!     header_block.truncate(len);
//!
//!     Ok(header_block)
//! }
//! ```

//! [RFC 9000]: https://www.rfc-editor.org/rfc/rfc9000.html
//! [RFC 9110]: https://www.rfc-editor.org/rfc/rfc9110.html
//! [RFC 9114]: https://www.rfc-editor.org/rfc/rfc9114.html
//! [RFC 9204]: https://www.rfc-editor.org/rfc/rfc9204.html
//! [malformed request]: https://datatracker.ietf.org/doc/html/rfc9114#section-4.1.2-3

use qlog::events::quic::PacketHeader;
use qlog::events::quic::PacketSent;
use qlog::events::quic::PacketType;
use qlog::events::quic::QuicFrame;
use qlog::events::EventData;
pub use quiche;
use quiche::h3::NameValue;

use smallvec::SmallVec;

/// The ID for an HTTP/3 control stream type.
///
/// See https://datatracker.ietf.org/doc/html/rfc9114#name-control-streams.
pub const HTTP3_CONTROL_STREAM_TYPE_ID: u64 = 0x0;

/// The ID for an HTTP/3 push stream type.
///
/// See https://datatracker.ietf.org/doc/html/rfc9114#name-push-streams.
pub const HTTP3_PUSH_STREAM_TYPE_ID: u64 = 0x1;

/// The ID for a QPACK encoder stream type.
///
/// See https://datatracker.ietf.org/doc/html/rfc9204#section-4.2-2.1.
pub const QPACK_ENCODER_STREAM_TYPE_ID: u64 = 0x2;

/// The ID for a QPACK decoder stream type.
///
/// See https://datatracker.ietf.org/doc/html/rfc9204#section-4.2-2.2.
pub const QPACK_DECODER_STREAM_TYPE_ID: u64 = 0x3;

#[derive(Default)]
struct StreamIdAllocator {
    id: u64,
}

impl StreamIdAllocator {
    pub fn take_next_id(&mut self) -> u64 {
        let old = self.id;
        self.id += 4;

        old
    }

    pub fn peek_next_id(&mut self) -> u64 {
        self.id
    }
}

fn encode_header_block(
    headers: &[quiche::h3::Header],
) -> std::result::Result<Vec<u8>, String> {
    let mut encoder = quiche::h3::qpack::Encoder::new();

    let headers_len = headers
        .iter()
        .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);

    let mut header_block = vec![0; headers_len];
    let len = encoder
        .encode(headers, &mut header_block)
        .map_err(|_| "Internal Error")?;

    header_block.truncate(len);

    Ok(header_block)
}

fn fake_packet_header() -> PacketHeader {
    PacketHeader {
        packet_type: PacketType::OneRtt,
        packet_number: None,
        flags: None,
        token: None,
        length: None,
        version: None,
        scil: None,
        dcil: None,
        scid: None,
        dcid: None,
    }
}

fn fake_packet_sent(frames: Option<SmallVec<[QuicFrame; 1]>>) -> EventData {
    EventData::PacketSent(PacketSent {
        header: fake_packet_header(),
        is_coalesced: None,
        retry_token: None,
        stateless_reset_token: None,
        supported_versions: None,
        raw: None,
        datagram_id: None,
        trigger: None,
        send_at_time: None,
        frames,
    })
}

pub mod actions;
pub mod client;
pub mod config;
pub mod frame;
pub mod frame_parser;
pub mod prompts;
pub mod recordreplay;