Skip to content

Commit f4720dc

Browse files
committed
Implement the extended CONNECT protocol from RFC 8441
1 parent f52d5e6 commit f4720dc

File tree

21 files changed

+650
-121
lines changed

21 files changed

+650
-121
lines changed

src/client.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,19 @@ where
517517
(response, stream)
518518
})
519519
}
520+
521+
/// Returns whether the [extended CONNECT protocol][1] is enabled or not.
522+
///
523+
/// This setting is configured by the server peer by sending the
524+
/// [`SETTINGS_ENABLE_CONNECT_PROTOCOL` parameter][2] in a `SETTINGS` frame.
525+
/// This method returns the currently acknowledged value recieved from the
526+
/// remote.
527+
///
528+
/// [1]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
529+
/// [2]: https://datatracker.ietf.org/doc/html/rfc8441#section-3
530+
pub fn is_extended_connect_protocol_enabled(&self) -> bool {
531+
self.inner.is_extended_connect_protocol_enabled()
532+
}
520533
}
521534

522535
impl<B> fmt::Debug for SendRequest<B>
@@ -1246,11 +1259,10 @@ where
12461259
/// This method returns the currently acknowledged value recieved from the
12471260
/// remote.
12481261
///
1249-
/// [settings]: https://tools.ietf.org/html/rfc7540#section-5.1.2
1262+
/// [1]: https://tools.ietf.org/html/rfc7540#section-5.1.2
12501263
pub fn max_concurrent_send_streams(&self) -> usize {
12511264
self.inner.max_send_streams()
12521265
}
1253-
12541266
/// Returns the maximum number of concurrent streams that may be initiated
12551267
/// by the server on this connection.
12561268
///
@@ -1426,6 +1438,7 @@ impl Peer {
14261438
uri,
14271439
headers,
14281440
version,
1441+
extensions,
14291442
..
14301443
},
14311444
_,
@@ -1435,7 +1448,7 @@ impl Peer {
14351448

14361449
// Build the set pseudo header set. All requests will include `method`
14371450
// and `path`.
1438-
let mut pseudo = Pseudo::request(method, uri);
1451+
let mut pseudo = Pseudo::request(method, uri, extensions);
14391452

14401453
if pseudo.scheme.is_none() {
14411454
// If the scheme is not set, then there are a two options.

src/ext.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//! Extensions specific to the HTTP/2.0 protocol.
2+
3+
use crate::hpack::BytesStr;
4+
5+
use bytes::Bytes;
6+
use std::fmt;
7+
8+
/// Represents the `:protocol` pseudo-header used by
9+
/// the [Extended CONNECT Protocol].
10+
///
11+
/// [Extended CONNECT Protocol]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
12+
#[derive(Clone, Eq, PartialEq)]
13+
pub struct PseudoProtocol {
14+
value: BytesStr,
15+
}
16+
17+
impl PseudoProtocol {
18+
/// Creates a new pseudo-protocol.
19+
///
20+
/// TODO: Validation.
21+
pub const fn from_static(value: &'static str) -> Self {
22+
Self {
23+
value: BytesStr::from_static(value),
24+
}
25+
}
26+
27+
pub(crate) fn as_str(&self) -> &str {
28+
self.value.as_str()
29+
}
30+
31+
pub(crate) fn try_from(bytes: Bytes) -> Result<Self, std::str::Utf8Error> {
32+
Ok(Self {
33+
value: BytesStr::try_from(bytes)?,
34+
})
35+
}
36+
}
37+
38+
impl<'a> From<&'a str> for PseudoProtocol {
39+
fn from(value: &'a str) -> Self {
40+
Self {
41+
value: BytesStr::from(value),
42+
}
43+
}
44+
}
45+
46+
impl std::ops::Deref for PseudoProtocol {
47+
type Target = str;
48+
fn deref(&self) -> &str {
49+
self.as_str()
50+
}
51+
}
52+
53+
impl AsRef<[u8]> for PseudoProtocol {
54+
fn as_ref(&self) -> &[u8] {
55+
self.value.as_ref()
56+
}
57+
}
58+
59+
impl fmt::Debug for PseudoProtocol {
60+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61+
self.value.fmt(f)
62+
}
63+
}

src/frame/headers.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use super::{util, StreamDependency, StreamId};
2+
use crate::ext::PseudoProtocol;
23
use crate::frame::{Error, Frame, Head, Kind};
34
use crate::hpack::{self, BytesStr};
45

56
use http::header::{self, HeaderName, HeaderValue};
6-
use http::{uri, HeaderMap, Method, Request, StatusCode, Uri};
7+
use http::{uri, Extensions, HeaderMap, Method, Request, StatusCode, Uri};
78

89
use bytes::{BufMut, Bytes, BytesMut};
910

@@ -66,6 +67,7 @@ pub struct Pseudo {
6667
pub scheme: Option<BytesStr>,
6768
pub authority: Option<BytesStr>,
6869
pub path: Option<BytesStr>,
70+
pub protocol: Option<PseudoProtocol>,
6971

7072
// Response
7173
pub status: Option<StatusCode>,
@@ -525,14 +527,16 @@ impl Continuation {
525527
// ===== impl Pseudo =====
526528

527529
impl Pseudo {
528-
pub fn request(method: Method, uri: Uri) -> Self {
530+
pub fn request(method: Method, uri: Uri, mut extensions: Extensions) -> Self {
529531
let parts = uri::Parts::from(uri);
530532

531533
let mut path = parts
532534
.path_and_query
533535
.map(|v| BytesStr::from(v.as_str()))
534536
.unwrap_or(BytesStr::from_static(""));
535537

538+
let protocol = extensions.remove::<PseudoProtocol>();
539+
536540
match method {
537541
Method::OPTIONS | Method::CONNECT => {}
538542
_ if path.is_empty() => {
@@ -546,6 +550,7 @@ impl Pseudo {
546550
scheme: None,
547551
authority: None,
548552
path: Some(path).filter(|p| !p.is_empty()),
553+
protocol,
549554
status: None,
550555
};
551556

@@ -571,6 +576,7 @@ impl Pseudo {
571576
scheme: None,
572577
authority: None,
573578
path: None,
579+
protocol: None,
574580
status: Some(status),
575581
}
576582
}
@@ -589,6 +595,11 @@ impl Pseudo {
589595
self.scheme = Some(bytes_str);
590596
}
591597

598+
#[cfg(feature = "unstable")]
599+
pub fn set_protocol(&mut self, protocol: PseudoProtocol) {
600+
self.protocol = Some(protocol);
601+
}
602+
592603
pub fn set_authority(&mut self, authority: BytesStr) {
593604
self.authority = Some(authority);
594605
}
@@ -677,6 +688,10 @@ impl Iterator for Iter {
677688
return Some(Path(path));
678689
}
679690

691+
if let Some(protocol) = pseudo.protocol.take() {
692+
return Some(Protocol(protocol));
693+
}
694+
680695
if let Some(status) = pseudo.status.take() {
681696
return Some(Status(status));
682697
}
@@ -875,6 +890,7 @@ impl HeaderBlock {
875890
Method(v) => set_pseudo!(method, v),
876891
Scheme(v) => set_pseudo!(scheme, v),
877892
Path(v) => set_pseudo!(path, v),
893+
Protocol(v) => set_pseudo!(protocol, v),
878894
Status(v) => set_pseudo!(status, v),
879895
}
880896
});

src/frame/settings.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub struct Settings {
1313
initial_window_size: Option<u32>,
1414
max_frame_size: Option<u32>,
1515
max_header_list_size: Option<u32>,
16+
enable_connect_protocol: Option<u32>,
1617
}
1718

1819
/// An enum that lists all valid settings that can be sent in a SETTINGS
@@ -27,6 +28,7 @@ pub enum Setting {
2728
InitialWindowSize(u32),
2829
MaxFrameSize(u32),
2930
MaxHeaderListSize(u32),
31+
EnableConnectProtocol(u32),
3032
}
3133

3234
#[derive(Copy, Clone, Eq, PartialEq, Default)]
@@ -107,6 +109,14 @@ impl Settings {
107109
self.enable_push = Some(enable as u32);
108110
}
109111

112+
pub fn is_extended_connect_protocol_enabled(&self) -> Option<bool> {
113+
self.enable_connect_protocol.map(|val| val != 0)
114+
}
115+
116+
pub fn set_enable_connect_protocol(&mut self, val: Option<u32>) {
117+
self.enable_connect_protocol = val;
118+
}
119+
110120
pub fn header_table_size(&self) -> Option<u32> {
111121
self.header_table_size
112122
}
@@ -149,7 +159,7 @@ impl Settings {
149159
debug_assert!(!settings.flags.is_ack());
150160

151161
for raw in payload.chunks(6) {
152-
match Setting::load(raw) {
162+
match dbg!(Setting::load(raw)) {
153163
Some(HeaderTableSize(val)) => {
154164
settings.header_table_size = Some(val);
155165
}
@@ -181,6 +191,14 @@ impl Settings {
181191
Some(MaxHeaderListSize(val)) => {
182192
settings.max_header_list_size = Some(val);
183193
}
194+
Some(EnableConnectProtocol(val)) => match val {
195+
0 | 1 => {
196+
settings.enable_connect_protocol = Some(val);
197+
}
198+
_ => {
199+
return Err(Error::InvalidSettingValue);
200+
}
201+
},
184202
None => {}
185203
}
186204
}
@@ -206,7 +224,7 @@ impl Settings {
206224
// Encode the settings
207225
self.for_each(|setting| {
208226
tracing::trace!("encoding setting; val={:?}", setting);
209-
setting.encode(dst)
227+
dbg!(setting).encode(dst)
210228
});
211229
}
212230

@@ -236,6 +254,10 @@ impl Settings {
236254
if let Some(v) = self.max_header_list_size {
237255
f(MaxHeaderListSize(v));
238256
}
257+
258+
if let Some(v) = self.enable_connect_protocol {
259+
f(EnableConnectProtocol(v));
260+
}
239261
}
240262
}
241263

@@ -269,6 +291,9 @@ impl fmt::Debug for Settings {
269291
Setting::MaxHeaderListSize(v) => {
270292
builder.field("max_header_list_size", &v);
271293
}
294+
Setting::EnableConnectProtocol(v) => {
295+
builder.field("enable_connect_protocol", &v);
296+
}
272297
});
273298

274299
builder.finish()
@@ -284,13 +309,14 @@ impl Setting {
284309
pub fn from_id(id: u16, val: u32) -> Option<Setting> {
285310
use self::Setting::*;
286311

287-
match id {
312+
match dbg!(id) {
288313
1 => Some(HeaderTableSize(val)),
289314
2 => Some(EnablePush(val)),
290315
3 => Some(MaxConcurrentStreams(val)),
291316
4 => Some(InitialWindowSize(val)),
292317
5 => Some(MaxFrameSize(val)),
293318
6 => Some(MaxHeaderListSize(val)),
319+
8 => Some(EnableConnectProtocol(val)),
294320
_ => None,
295321
}
296322
}
@@ -322,6 +348,7 @@ impl Setting {
322348
InitialWindowSize(v) => (4, v),
323349
MaxFrameSize(v) => (5, v),
324350
MaxHeaderListSize(v) => (6, v),
351+
EnableConnectProtocol(v) => (8, v),
325352
};
326353

327354
dst.put_u16(kind);

0 commit comments

Comments
 (0)