Skip to content

Commit 87969c1

Browse files
authored
Implement the extended CONNECT protocol from RFC 8441 (#565)
1 parent dbaa3a4 commit 87969c1

File tree

22 files changed

+691
-117
lines changed

22 files changed

+691
-117
lines changed

src/client.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
//! [`Error`]: ../struct.Error.html
137137
138138
use crate::codec::{Codec, SendError, UserError};
139+
use crate::ext::Protocol;
139140
use crate::frame::{Headers, Pseudo, Reason, Settings, StreamId};
140141
use crate::proto::{self, Error};
141142
use crate::{FlowControl, PingPong, RecvStream, SendStream};
@@ -517,6 +518,19 @@ where
517518
(response, stream)
518519
})
519520
}
521+
522+
/// Returns whether the [extended CONNECT protocol][1] is enabled or not.
523+
///
524+
/// This setting is configured by the server peer by sending the
525+
/// [`SETTINGS_ENABLE_CONNECT_PROTOCOL` parameter][2] in a `SETTINGS` frame.
526+
/// This method returns the currently acknowledged value recieved from the
527+
/// remote.
528+
///
529+
/// [1]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
530+
/// [2]: https://datatracker.ietf.org/doc/html/rfc8441#section-3
531+
pub fn is_extended_connect_protocol_enabled(&self) -> bool {
532+
self.inner.is_extended_connect_protocol_enabled()
533+
}
520534
}
521535

522536
impl<B> fmt::Debug for SendRequest<B>
@@ -1246,11 +1260,10 @@ where
12461260
/// This method returns the currently acknowledged value recieved from the
12471261
/// remote.
12481262
///
1249-
/// [settings]: https://tools.ietf.org/html/rfc7540#section-5.1.2
1263+
/// [1]: https://tools.ietf.org/html/rfc7540#section-5.1.2
12501264
pub fn max_concurrent_send_streams(&self) -> usize {
12511265
self.inner.max_send_streams()
12521266
}
1253-
12541267
/// Returns the maximum number of concurrent streams that may be initiated
12551268
/// by the server on this connection.
12561269
///
@@ -1416,6 +1429,7 @@ impl Peer {
14161429
pub fn convert_send_message(
14171430
id: StreamId,
14181431
request: Request<()>,
1432+
protocol: Option<Protocol>,
14191433
end_of_stream: bool,
14201434
) -> Result<Headers, SendError> {
14211435
use http::request::Parts;
@@ -1435,7 +1449,7 @@ impl Peer {
14351449

14361450
// Build the set pseudo header set. All requests will include `method`
14371451
// and `path`.
1438-
let mut pseudo = Pseudo::request(method, uri);
1452+
let mut pseudo = Pseudo::request(method, uri, protocol);
14391453

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

src/ext.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//! Extensions specific to the HTTP/2 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 Protocol {
14+
value: BytesStr,
15+
}
16+
17+
impl Protocol {
18+
/// Converts a static string to a protocol name.
19+
pub const fn from_static(value: &'static str) -> Self {
20+
Self {
21+
value: BytesStr::from_static(value),
22+
}
23+
}
24+
25+
/// Returns a str representation of the header.
26+
pub fn as_str(&self) -> &str {
27+
self.value.as_str()
28+
}
29+
30+
pub(crate) fn try_from(bytes: Bytes) -> Result<Self, std::str::Utf8Error> {
31+
Ok(Self {
32+
value: BytesStr::try_from(bytes)?,
33+
})
34+
}
35+
}
36+
37+
impl<'a> From<&'a str> for Protocol {
38+
fn from(value: &'a str) -> Self {
39+
Self {
40+
value: BytesStr::from(value),
41+
}
42+
}
43+
}
44+
45+
impl AsRef<[u8]> for Protocol {
46+
fn as_ref(&self) -> &[u8] {
47+
self.value.as_ref()
48+
}
49+
}
50+
51+
impl fmt::Debug for Protocol {
52+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53+
self.value.fmt(f)
54+
}
55+
}

src/frame/headers.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::{util, StreamDependency, StreamId};
2+
use crate::ext::Protocol;
23
use crate::frame::{Error, Frame, Head, Kind};
34
use crate::hpack::{self, BytesStr};
45

@@ -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<Protocol>,
6971

7072
// Response
7173
pub status: Option<StatusCode>,
@@ -292,6 +294,10 @@ impl fmt::Debug for Headers {
292294
.field("stream_id", &self.stream_id)
293295
.field("flags", &self.flags);
294296

297+
if let Some(ref protocol) = self.header_block.pseudo.protocol {
298+
builder.field("protocol", protocol);
299+
}
300+
295301
if let Some(ref dep) = self.stream_dep {
296302
builder.field("stream_dep", dep);
297303
}
@@ -529,7 +535,7 @@ impl Continuation {
529535
// ===== impl Pseudo =====
530536

531537
impl Pseudo {
532-
pub fn request(method: Method, uri: Uri) -> Self {
538+
pub fn request(method: Method, uri: Uri, protocol: Option<Protocol>) -> Self {
533539
let parts = uri::Parts::from(uri);
534540

535541
let mut path = parts
@@ -550,6 +556,7 @@ impl Pseudo {
550556
scheme: None,
551557
authority: None,
552558
path: Some(path).filter(|p| !p.is_empty()),
559+
protocol,
553560
status: None,
554561
};
555562

@@ -575,6 +582,7 @@ impl Pseudo {
575582
scheme: None,
576583
authority: None,
577584
path: None,
585+
protocol: None,
578586
status: Some(status),
579587
}
580588
}
@@ -593,6 +601,11 @@ impl Pseudo {
593601
self.scheme = Some(bytes_str);
594602
}
595603

604+
#[cfg(feature = "unstable")]
605+
pub fn set_protocol(&mut self, protocol: Protocol) {
606+
self.protocol = Some(protocol);
607+
}
608+
596609
pub fn set_authority(&mut self, authority: BytesStr) {
597610
self.authority = Some(authority);
598611
}
@@ -681,6 +694,10 @@ impl Iterator for Iter {
681694
return Some(Path(path));
682695
}
683696

697+
if let Some(protocol) = pseudo.protocol.take() {
698+
return Some(Protocol(protocol));
699+
}
700+
684701
if let Some(status) = pseudo.status.take() {
685702
return Some(Status(status));
686703
}
@@ -879,6 +896,7 @@ impl HeaderBlock {
879896
Method(v) => set_pseudo!(method, v),
880897
Scheme(v) => set_pseudo!(scheme, v),
881898
Path(v) => set_pseudo!(path, v),
899+
Protocol(v) => set_pseudo!(protocol, v),
882900
Status(v) => set_pseudo!(status, v),
883901
}
884902
});

src/frame/settings.rs

Lines changed: 27 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,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
}
@@ -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
}
@@ -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()
@@ -291,6 +316,7 @@ impl Setting {
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);

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::Protocol;
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(Protocol),
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 = Protocol::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.as_str().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(Protocol::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)