Skip to content

Commit 3a62294

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

File tree

19 files changed

+579
-102
lines changed

19 files changed

+579
-102
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: 28 additions & 0 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,15 @@ impl Settings {
107109
self.enable_push = Some(enable as u32);
108110
}
109111

112+
#[cfg(feature = "unstable")]
113+
pub fn is_connect_protocol_enabled(&self) -> Option<bool> {
114+
self.enable_connect_protocol.map(|val| val != 0)
115+
}
116+
117+
pub fn set_enable_connect_protocol(&mut self, val: Option<u32>) {
118+
self.enable_connect_protocol = val;
119+
}
120+
110121
pub fn header_table_size(&self) -> Option<u32> {
111122
self.header_table_size
112123
}
@@ -181,6 +192,14 @@ impl Settings {
181192
Some(MaxHeaderListSize(val)) => {
182193
settings.max_header_list_size = Some(val);
183194
}
195+
Some(EnableConnectProtocol(val)) => match val {
196+
0 | 1 => {
197+
settings.enable_connect_protocol = Some(val);
198+
}
199+
_ => {
200+
return Err(Error::InvalidSettingValue);
201+
}
202+
},
184203
None => {}
185204
}
186205
}
@@ -236,6 +255,10 @@ impl Settings {
236255
if let Some(v) = self.max_header_list_size {
237256
f(MaxHeaderListSize(v));
238257
}
258+
259+
if let Some(v) = self.enable_connect_protocol {
260+
f(EnableConnectProtocol(v));
261+
}
239262
}
240263
}
241264

@@ -269,6 +292,9 @@ impl fmt::Debug for Settings {
269292
Setting::MaxHeaderListSize(v) => {
270293
builder.field("max_header_list_size", &v);
271294
}
295+
Setting::EnableConnectProtocol(v) => {
296+
builder.field("enable_connect_protocol", &v);
297+
}
272298
});
273299

274300
builder.finish()
@@ -291,6 +317,7 @@ impl Setting {
291317
4 => Some(InitialWindowSize(val)),
292318
5 => Some(MaxFrameSize(val)),
293319
6 => Some(MaxHeaderListSize(val)),
320+
8 => Some(EnableConnectProtocol(val)),
294321
_ => None,
295322
}
296323
}
@@ -322,6 +349,7 @@ impl Setting {
322349
InitialWindowSize(v) => (4, v),
323350
MaxFrameSize(v) => (5, v),
324351
MaxHeaderListSize(v) => (6, v),
352+
EnableConnectProtocol(v) => (8, v),
325353
};
326354

327355
dst.put_u16(kind);

src/hpack/header.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::{DecoderError, NeedMore};
2+
use crate::ext::PseudoProtocol;
23

34
use bytes::Bytes;
45
use http::header::{HeaderName, HeaderValue};
@@ -14,6 +15,7 @@ pub enum Header<T = HeaderName> {
1415
Method(Method),
1516
Scheme(BytesStr),
1617
Path(BytesStr),
18+
Protocol(PseudoProtocol),
1719
Status(StatusCode),
1820
}
1921

@@ -25,6 +27,7 @@ pub enum Name<'a> {
2527
Method,
2628
Scheme,
2729
Path,
30+
Protocol,
2831
Status,
2932
}
3033

@@ -51,6 +54,7 @@ impl Header<Option<HeaderName>> {
5154
Method(v) => Method(v),
5255
Scheme(v) => Scheme(v),
5356
Path(v) => Path(v),
57+
Protocol(v) => Protocol(v),
5458
Status(v) => Status(v),
5559
})
5660
}
@@ -79,6 +83,10 @@ impl Header {
7983
let value = BytesStr::try_from(value)?;
8084
Ok(Header::Path(value))
8185
}
86+
b"protocol" => {
87+
let value = PseudoProtocol::try_from(value)?;
88+
Ok(Header::Protocol(value))
89+
}
8290
b"status" => {
8391
let status = StatusCode::from_bytes(&value)?;
8492
Ok(Header::Status(status))
@@ -104,6 +112,7 @@ impl Header {
104112
Header::Method(ref v) => 32 + 7 + v.as_ref().len(),
105113
Header::Scheme(ref v) => 32 + 7 + v.len(),
106114
Header::Path(ref v) => 32 + 5 + v.len(),
115+
Header::Protocol(ref v) => 32 + 9 + v.len(),
107116
Header::Status(_) => 32 + 7 + 3,
108117
}
109118
}
@@ -116,6 +125,7 @@ impl Header {
116125
Header::Method(..) => Name::Method,
117126
Header::Scheme(..) => Name::Scheme,
118127
Header::Path(..) => Name::Path,
128+
Header::Protocol(..) => Name::Protocol,
119129
Header::Status(..) => Name::Status,
120130
}
121131
}
@@ -127,6 +137,7 @@ impl Header {
127137
Header::Method(ref v) => v.as_ref().as_ref(),
128138
Header::Scheme(ref v) => v.as_ref(),
129139
Header::Path(ref v) => v.as_ref(),
140+
Header::Protocol(ref v) => v.as_ref(),
130141
Header::Status(ref v) => v.as_str().as_ref(),
131142
}
132143
}
@@ -156,6 +167,10 @@ impl Header {
156167
Header::Path(ref b) => a == b,
157168
_ => false,
158169
},
170+
Header::Protocol(ref a) => match *other {
171+
Header::Protocol(ref b) => a == b,
172+
_ => false,
173+
},
159174
Header::Status(ref a) => match *other {
160175
Header::Status(ref b) => a == b,
161176
_ => false,
@@ -205,6 +220,7 @@ impl From<Header> for Header<Option<HeaderName>> {
205220
Header::Method(v) => Header::Method(v),
206221
Header::Scheme(v) => Header::Scheme(v),
207222
Header::Path(v) => Header::Path(v),
223+
Header::Protocol(v) => Header::Protocol(v),
208224
Header::Status(v) => Header::Status(v),
209225
}
210226
}
@@ -221,6 +237,7 @@ impl<'a> Name<'a> {
221237
Name::Method => Ok(Header::Method(Method::from_bytes(&*value)?)),
222238
Name::Scheme => Ok(Header::Scheme(BytesStr::try_from(value)?)),
223239
Name::Path => Ok(Header::Path(BytesStr::try_from(value)?)),
240+
Name::Protocol => Ok(Header::Protocol(PseudoProtocol::try_from(value)?)),
224241
Name::Status => {
225242
match StatusCode::from_bytes(&value) {
226243
Ok(status) => Ok(Header::Status(status)),
@@ -238,6 +255,7 @@ impl<'a> Name<'a> {
238255
Name::Method => b":method",
239256
Name::Scheme => b":scheme",
240257
Name::Path => b":path",
258+
Name::Protocol => b":protocol",
241259
Name::Status => b":status",
242260
}
243261
}

src/hpack/table.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,7 @@ fn index_static(header: &Header) -> Option<(usize, bool)> {
751751
"/index.html" => Some((5, true)),
752752
_ => Some((4, false)),
753753
},
754+
Header::Protocol(..) => None,
754755
Header::Status(ref v) => match u16::from(*v) {
755756
200 => Some((8, true)),
756757
204 => Some((9, true)),

0 commit comments

Comments
 (0)