Skip to content

Commit f8989b7

Browse files
committed
Implement the extended CONNECT protocol from RFC 8441
1 parent ce81583 commit f8989b7

File tree

21 files changed

+668
-118
lines changed

21 files changed

+668
-118
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: 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 Protocol {
14+
value: BytesStr,
15+
}
16+
17+
impl Protocol {
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 Protocol {
39+
fn from(value: &'a str) -> Self {
40+
Self {
41+
value: BytesStr::from(value),
42+
}
43+
}
44+
}
45+
46+
impl std::ops::Deref for Protocol {
47+
type Target = str;
48+
fn deref(&self) -> &str {
49+
self.as_str()
50+
}
51+
}
52+
53+
impl AsRef<[u8]> for Protocol {
54+
fn as_ref(&self) -> &[u8] {
55+
self.value.as_ref()
56+
}
57+
}
58+
59+
impl fmt::Debug for Protocol {
60+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61+
self.value.fmt(f)
62+
}
63+
}

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>,
@@ -288,6 +290,10 @@ impl fmt::Debug for Headers {
288290
.field("stream_id", &self.stream_id)
289291
.field("flags", &self.flags);
290292

293+
if let Some(ref protocol) = self.header_block.pseudo.protocol {
294+
builder.field("protocol", protocol);
295+
}
296+
291297
if let Some(ref dep) = self.stream_dep {
292298
builder.field("stream_dep", dep);
293299
}
@@ -525,7 +531,7 @@ impl Continuation {
525531
// ===== impl Pseudo =====
526532

527533
impl Pseudo {
528-
pub fn request(method: Method, uri: Uri) -> Self {
534+
pub fn request(method: Method, uri: Uri, protocol: Option<Protocol>) -> Self {
529535
let parts = uri::Parts::from(uri);
530536

531537
let mut path = parts
@@ -546,6 +552,7 @@ impl Pseudo {
546552
scheme: None,
547553
authority: None,
548554
path: Some(path).filter(|p| !p.is_empty()),
555+
protocol,
549556
status: None,
550557
};
551558

@@ -571,6 +578,7 @@ impl Pseudo {
571578
scheme: None,
572579
authority: None,
573580
path: None,
581+
protocol: None,
574582
status: Some(status),
575583
}
576584
}
@@ -589,6 +597,11 @@ impl Pseudo {
589597
self.scheme = Some(bytes_str);
590598
}
591599

600+
#[cfg(feature = "unstable")]
601+
pub fn set_protocol(&mut self, protocol: Protocol) {
602+
self.protocol = Some(protocol);
603+
}
604+
592605
pub fn set_authority(&mut self, authority: BytesStr) {
593606
self.authority = Some(authority);
594607
}
@@ -677,6 +690,10 @@ impl Iterator for Iter {
677690
return Some(Path(path));
678691
}
679692

693+
if let Some(protocol) = pseudo.protocol.take() {
694+
return Some(Protocol(protocol));
695+
}
696+
680697
if let Some(status) = pseudo.status.take() {
681698
return Some(Status(status));
682699
}
@@ -875,6 +892,7 @@ impl HeaderBlock {
875892
Method(v) => set_pseudo!(method, v),
876893
Scheme(v) => set_pseudo!(scheme, v),
877894
Path(v) => set_pseudo!(path, v),
895+
Protocol(v) => set_pseudo!(protocol, v),
878896
Status(v) => set_pseudo!(status, v),
879897
}
880898
});

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.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)