diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0952337e4..c8d359c9d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -31,7 +31,6 @@ jobs: strategy: matrix: rust: - - nightly - beta - stable steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index edca1a224..4abbf24ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,14 @@ * Update to `http` 1.0. * Remove deprecated `Server::poll_close()`. +# 0.3.24 (January 17, 2024) + +* Limit error resets for misbehaving connections. + +# 0.3.23 (January 10, 2024) + +* Backport fix from 0.4.1 for stream capacity assignment. + # 0.3.22 (November 15, 2023) * Add `header_table_size(usize)` option to client and server builders. diff --git a/Cargo.toml b/Cargo.toml index 9d35b5a68..bc578afca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "h2" +name = "h2-patch" # When releasing to crates.io: # - Update CHANGELOG.md. # - Create git tag -version = "0.4.5" +version = "0.5.7" license = "MIT" authors = [ "Carl Lerche ", @@ -45,8 +45,8 @@ futures-sink = { version = "0.3", default-features = false } tokio-util = { version = "0.7.1", features = ["codec", "io"] } tokio = { version = "1", features = ["io-util"] } bytes = "1" -http = "1" -tracing = { version = "0.1.35", default-features = false, features = ["std"] } +http = "0.2" +tracing = { version = "0.1.32", default-features = false, features = ["std"] } fnv = "1.0.5" slab = "0.4.2" indexmap = { version = "2", features = ["std"] } @@ -66,6 +66,7 @@ serde_json = "1.0.0" # Examples tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "net"] } env_logger = { version = "0.10", default-features = false } + tokio-rustls = "0.26" webpki-roots = "0.26" diff --git a/benches/main.rs b/benches/main.rs index b1e64edf4..555f26dfc 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -1,3 +1,4 @@ +use h2_patch as h2; use bytes::Bytes; use h2::{ client, diff --git a/examples/akamai.rs b/examples/akamai.rs index 385bcef75..af626a7bd 100644 --- a/examples/akamai.rs +++ b/examples/akamai.rs @@ -1,4 +1,5 @@ use h2::client; +use h2_patch as h2; use http::{Method, Request}; use tokio::net::TcpStream; use tokio_rustls::TlsConnector; diff --git a/examples/client.rs b/examples/client.rs index 61e237aa3..c9514af12 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -1,4 +1,5 @@ use h2::client; +use h2_patch as h2; use http::{HeaderMap, Request}; use std::error::Error; diff --git a/examples/server.rs b/examples/server.rs index 6d6490db0..cce0ad25b 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,3 +1,4 @@ +use h2_patch as h2; use std::error::Error; use bytes::Bytes; diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 922eca238..aafb60ae7 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -16,7 +16,7 @@ tokio = { version = "1", features = [ "full" ] } h2 = { path = "../", features = [ "unstable" ] } h2-support = { path = "../tests/h2-support" } futures = { version = "0.3", default-features = false, features = ["std"] } -http = "1" +http = "0.2" # Prevent this from interfering with workspaces [workspace] diff --git a/src/client.rs b/src/client.rs index ffeda6077..ce9f44299 100644 --- a/src/client.rs +++ b/src/client.rs @@ -138,6 +138,7 @@ use crate::codec::{Codec, SendError, UserError}; use crate::ext::Protocol; use crate::frame::{Headers, Pseudo, Reason, Settings, StreamId}; +use crate::profile::AgentProfile; use crate::proto::{self, Error}; use crate::{FlowControl, PingPong, RecvStream, SendStream}; @@ -175,6 +176,7 @@ use tracing::Instrument; pub struct SendRequest { inner: proto::Streams, pending: Option, + profile: AgentProfile, } /// Returns a `SendRequest` instance once it is ready to send at least one @@ -343,6 +345,9 @@ pub struct Builder { /// /// When this gets exceeded, we issue GOAWAYs. local_max_error_reset_streams: Option, + + /// Profile Settings + _profile: AgentProfile, } #[derive(Debug)] @@ -515,7 +520,12 @@ where end_of_stream: bool, ) -> Result<(ResponseFuture, SendStream), crate::Error> { self.inner - .send_request(request, end_of_stream, self.pending.as_ref()) + .send_request( + request, + end_of_stream, + self.pending.as_ref(), + self.profile.clone(), + ) .map_err(Into::into) .map(|(stream, is_full)| { if stream.is_pending_open() && is_full { @@ -576,6 +586,7 @@ where SendRequest { inner: self.inner.clone(), pending: None, + profile: self.profile.clone(), } } } @@ -663,9 +674,16 @@ impl Builder { settings: Default::default(), stream_id: 1.into(), local_max_error_reset_streams: Some(proto::DEFAULT_LOCAL_RESET_COUNT_MAX), + _profile: AgentProfile::default(), } } + /// Use the profile to configure the client. + pub fn profile(&mut self, profile: AgentProfile) -> &mut Self { + self._profile = profile; + self + } + /// Indicates the initial window size (in octets) for stream-level /// flow control for received data. /// @@ -1321,7 +1339,7 @@ where // Send initial settings frame codec - .buffer(builder.settings.clone().into()) + .buffer((builder.settings.clone(), builder._profile.clone()).into()) .expect("invalid SETTINGS frame"); let inner = proto::Connection::new( @@ -1340,6 +1358,7 @@ where let send_request = SendRequest { inner: inner.streams().clone(), pending: None, + profile: builder._profile, }; let mut connection = Connection { inner }; @@ -1584,6 +1603,7 @@ impl Peer { request: Request<()>, protocol: Option, end_of_stream: bool, + profile: AgentProfile, ) -> Result { use http::request::Parts; @@ -1602,7 +1622,7 @@ impl Peer { // Build the set pseudo header set. All requests will include `method` // and `path`. - let mut pseudo = Pseudo::request(method, uri, protocol); + let mut pseudo = Pseudo::request(method, uri, protocol, profile); if pseudo.scheme.is_none() { // If the scheme is not set, then there are a two options. diff --git a/src/codec/framed_write.rs b/src/codec/framed_write.rs index c88af02da..d26426d49 100644 --- a/src/codec/framed_write.rs +++ b/src/codec/framed_write.rs @@ -257,8 +257,8 @@ where self.next = Some(Next::Continuation(continuation)); } } - Frame::Settings(v) => { - v.encode(self.buf.get_mut()); + Frame::Settings(v, _profile) => { + v.encode(self.buf.get_mut(), _profile); tracing::trace!(rem = self.buf.remaining(), "encoded settings"); } Frame::GoAway(v) => { diff --git a/src/frame/headers.rs b/src/frame/headers.rs index 0c756325f..0d7eed866 100644 --- a/src/frame/headers.rs +++ b/src/frame/headers.rs @@ -2,6 +2,7 @@ use super::{util, StreamDependency, StreamId}; use crate::ext::Protocol; use crate::frame::{Error, Frame, Head, Kind}; use crate::hpack::{self, BytesStr}; +use crate::profile::AgentProfile; use http::header::{self, HeaderName, HeaderValue}; use http::{uri, HeaderMap, Method, Request, StatusCode, Uri}; @@ -72,6 +73,17 @@ pub struct Pseudo { // Response pub status: Option, + + // Profile + pub profile: AgentProfile, +} + +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum PseudoType { + Method, + Scheme, + Authority, + Path, } #[derive(Debug)] @@ -117,7 +129,7 @@ impl Headers { pub fn new(stream_id: StreamId, pseudo: Pseudo, fields: HeaderMap) -> Self { Headers { stream_id, - stream_dep: None, + stream_dep: Some(pseudo.profile.to_stream_dependency()), header_block: HeaderBlock { field_size: calculate_headermap_size(&fields), fields, @@ -280,7 +292,12 @@ impl Headers { self.header_block .into_encoding(encoder) - .encode(&head, dst, |_| {}) + .encode(head, dst, |dst| { + if let Some(ref stream_dep) = self.stream_dep { + // write 5 bytes for the stream dependency + stream_dep.encode(dst); + } + }) } fn head(&self) -> Head { @@ -501,7 +518,7 @@ impl PushPromise { self.header_block .into_encoding(encoder) - .encode(&head, dst, |dst| { + .encode(head, dst, |dst| { dst.put_u32(promised_id.into()); }) } @@ -544,14 +561,19 @@ impl Continuation { // Get the CONTINUATION frame head let head = self.head(); - self.header_block.encode(&head, dst, |_| {}) + self.header_block.encode(head, dst, |_| {}) } } // ===== impl Pseudo ===== impl Pseudo { - pub fn request(method: Method, uri: Uri, protocol: Option) -> Self { + pub fn request( + method: Method, + uri: Uri, + protocol: Option, + profile: AgentProfile, + ) -> Self { let parts = uri::Parts::from(uri); let (scheme, path) = if method == Method::CONNECT && protocol.is_none() { @@ -580,6 +602,7 @@ impl Pseudo { path, protocol, status: None, + profile, }; // If the URI includes a scheme component, add it to the pseudo headers @@ -604,6 +627,7 @@ impl Pseudo { path: None, protocol: None, status: Some(status), + profile: AgentProfile::default(), } } @@ -640,7 +664,7 @@ impl Pseudo { // ===== impl EncodingHeaderBlock ===== impl EncodingHeaderBlock { - fn encode(mut self, head: &Head, dst: &mut EncodeBuf<'_>, f: F) -> Option + fn encode(mut self, head: Head, dst: &mut EncodeBuf<'_>, f: F) -> Option where F: FnOnce(&mut EncodeBuf<'_>), { @@ -698,20 +722,29 @@ impl Iterator for Iter { use crate::hpack::Header::*; if let Some(ref mut pseudo) = self.pseudo { - if let Some(method) = pseudo.method.take() { - return Some(Method(method)); - } - - if let Some(scheme) = pseudo.scheme.take() { - return Some(Scheme(scheme)); - } - - if let Some(authority) = pseudo.authority.take() { - return Some(Authority(authority)); - } - - if let Some(path) = pseudo.path.take() { - return Some(Path(path)); + for pseudo_type in pseudo.profile.to_pseudo() { + match pseudo_type { + PseudoType::Method => { + if let Some(method) = pseudo.method.take() { + return Some(Method(method)); + } + } + PseudoType::Scheme => { + if let Some(scheme) = pseudo.scheme.take() { + return Some(Scheme(scheme)); + } + } + PseudoType::Authority => { + if let Some(authority) = pseudo.authority.take() { + return Some(Authority(authority)); + } + } + PseudoType::Path => { + if let Some(path) = pseudo.path.take() { + return Some(Path(path)); + } + } + } } if let Some(protocol) = pseudo.protocol.take() { @@ -768,9 +801,9 @@ impl HeadersFlag { } impl Default for HeadersFlag { - /// Returns a `HeadersFlag` value with `END_HEADERS` set. + /// Returns a `HeadersFlag` value with `END_HEADERS` and `PRIORITY` set. fn default() -> Self { - HeadersFlag(END_HEADERS) + HeadersFlag(END_HEADERS | PRIORITY) } } @@ -1062,9 +1095,11 @@ mod test { Pseudo::request( Method::CONNECT, Uri::from_static("https://example.com:8443"), - None + None, + Default::default(), ), Pseudo { + profile: AgentProfile::default(), method: Method::CONNECT.into(), authority: BytesStr::from_static("example.com:8443").into(), ..Default::default() @@ -1075,7 +1110,8 @@ mod test { Pseudo::request( Method::CONNECT, Uri::from_static("https://example.com/test"), - None + None, + Default::default(), ), Pseudo { method: Method::CONNECT.into(), @@ -1085,7 +1121,12 @@ mod test { ); assert_eq!( - Pseudo::request(Method::CONNECT, Uri::from_static("example.com:8443"), None), + Pseudo::request( + Method::CONNECT, + Uri::from_static("example.com:8443"), + None, + Default::default(), + ), Pseudo { method: Method::CONNECT.into(), authority: BytesStr::from_static("example.com:8443").into(), @@ -1105,7 +1146,8 @@ mod test { Pseudo::request( Method::CONNECT, Uri::from_static("https://example.com:8443"), - Protocol::from_static("the-bread-protocol").into() + Protocol::from_static("the-bread-protocol").into(), + Default::default(), ), Pseudo { method: Method::CONNECT.into(), @@ -1121,7 +1163,8 @@ mod test { Pseudo::request( Method::CONNECT, Uri::from_static("https://example.com:8443/test"), - Protocol::from_static("the-bread-protocol").into() + Protocol::from_static("the-bread-protocol").into(), + Default::default(), ), Pseudo { method: Method::CONNECT.into(), @@ -1137,7 +1180,8 @@ mod test { Pseudo::request( Method::CONNECT, Uri::from_static("http://example.com/a/b/c"), - Protocol::from_static("the-bread-protocol").into() + Protocol::from_static("the-bread-protocol").into(), + Default::default(), ), Pseudo { method: Method::CONNECT.into(), @@ -1156,7 +1200,12 @@ mod test { // these MUST include a ":path" pseudo-header field with a value of '*' (see Section 7.1 of [HTTP]). // See: https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1 assert_eq!( - Pseudo::request(Method::OPTIONS, Uri::from_static("example.com:8080"), None,), + Pseudo::request( + Method::OPTIONS, + Uri::from_static("example.com:8080"), + None, + Default::default(), + ), Pseudo { method: Method::OPTIONS.into(), authority: BytesStr::from_static("example.com:8080").into(), @@ -1184,6 +1233,7 @@ mod test { method.clone(), Uri::from_static("http://example.com:8080"), None, + Default::default(), ), Pseudo { method: method.clone().into(), @@ -1198,6 +1248,7 @@ mod test { method.clone(), Uri::from_static("https://example.com/a/b/c"), None, + Default::default(), ), Pseudo { method: method.into(), diff --git a/src/frame/mod.rs b/src/frame/mod.rs index 0e8e7035c..274167c27 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -1,4 +1,5 @@ use crate::hpack; +use crate::profile::AgentProfile; use bytes::Bytes; @@ -51,6 +52,7 @@ mod window_update; pub use self::data::Data; pub use self::go_away::GoAway; pub use self::head::{Head, Kind}; +pub(crate) use self::headers::PseudoType; pub use self::headers::{ parse_u64, Continuation, Headers, Pseudo, PushPromise, PushPromiseHeaderError, }; @@ -82,7 +84,7 @@ pub enum Frame { Headers(Headers), Priority(Priority), PushPromise(PushPromise), - Settings(Settings), + Settings(Settings, AgentProfile), Ping(Ping), GoAway(GoAway), WindowUpdate(WindowUpdate), @@ -101,7 +103,7 @@ impl Frame { Headers(frame) => frame.into(), Priority(frame) => frame.into(), PushPromise(frame) => frame.into(), - Settings(frame) => frame.into(), + Settings(frame, _) => frame.into(), Ping(frame) => frame.into(), GoAway(frame) => frame.into(), WindowUpdate(frame) => frame.into(), @@ -119,7 +121,7 @@ impl fmt::Debug for Frame { Headers(ref frame) => fmt::Debug::fmt(frame, fmt), Priority(ref frame) => fmt::Debug::fmt(frame, fmt), PushPromise(ref frame) => fmt::Debug::fmt(frame, fmt), - Settings(ref frame) => fmt::Debug::fmt(frame, fmt), + Settings(ref frame, _) => fmt::Debug::fmt(frame, fmt), Ping(ref frame) => fmt::Debug::fmt(frame, fmt), GoAway(ref frame) => fmt::Debug::fmt(frame, fmt), WindowUpdate(ref frame) => fmt::Debug::fmt(frame, fmt), diff --git a/src/frame/priority.rs b/src/frame/priority.rs index d7d47dbb0..7feb50a85 100644 --- a/src/frame/priority.rs +++ b/src/frame/priority.rs @@ -1,3 +1,5 @@ +use bytes::BufMut; + use crate::frame::*; #[derive(Debug, Eq, PartialEq)] @@ -69,4 +71,15 @@ impl StreamDependency { pub fn dependency_id(&self) -> StreamId { self.dependency_id } + + pub fn encode(&self, dst: &mut T) { + let mut buf = [0; 4]; + let dependency_id: u32 = self.dependency_id().into(); + buf[0..4].copy_from_slice(&dependency_id.to_be_bytes()); + if self.is_exclusive { + buf[0] |= 0x80; + } + dst.put_slice(&buf); + dst.put_u8(self.weight); + } } diff --git a/src/frame/settings.rs b/src/frame/settings.rs index 484498a9d..56882d116 100644 --- a/src/frame/settings.rs +++ b/src/frame/settings.rs @@ -1,6 +1,9 @@ use std::fmt; -use crate::frame::{util, Error, Frame, FrameSize, Head, Kind, StreamId}; +use crate::{ + frame::{util, Error, Frame, FrameSize, Head, Kind, StreamId}, + profile::AgentProfile, +}; use bytes::{BufMut, BytesMut}; #[derive(Clone, Default, Eq, PartialEq)] @@ -206,11 +209,11 @@ impl Settings { fn payload_len(&self) -> usize { let mut len = 0; - self.for_each(|_| len += 6); + self.for_each(|_| len += 6, AgentProfile::default()); len } - pub fn encode(&self, dst: &mut BytesMut) { + pub fn encode(&self, dst: &mut BytesMut, _profile: AgentProfile) { // Create & encode an appropriate frame head let head = Head::new(Kind::Settings, self.flags.into(), StreamId::zero()); let payload_len = self.payload_len(); @@ -220,13 +223,16 @@ impl Settings { head.encode(payload_len, dst); // Encode the settings - self.for_each(|setting| { - tracing::trace!("encoding setting; val={:?}", setting); - setting.encode(dst) - }); + self.for_each( + |setting| { + tracing::trace!("encoding setting; val={:?}", setting); + setting.encode(dst) + }, + _profile, + ); } - fn for_each(&self, mut f: F) { + fn for_each(&self, mut f: F, _profile: AgentProfile) { use self::Setting::*; if let Some(v) = self.header_table_size { @@ -237,12 +243,25 @@ impl Settings { f(EnablePush(v)); } - if let Some(v) = self.max_concurrent_streams { - f(MaxConcurrentStreams(v)); - } + match _profile { + AgentProfile::Safari => { + if let Some(v) = self.initial_window_size { + f(InitialWindowSize(v)); + } - if let Some(v) = self.initial_window_size { - f(InitialWindowSize(v)); + if let Some(v) = self.max_concurrent_streams { + f(MaxConcurrentStreams(v)); + } + } + AgentProfile::Chrome | AgentProfile::OkHttp | AgentProfile::Edge | _ => { + if let Some(v) = self.max_concurrent_streams { + f(MaxConcurrentStreams(v)); + } + + if let Some(v) = self.initial_window_size { + f(InitialWindowSize(v)); + } + } } if let Some(v) = self.max_frame_size { @@ -261,7 +280,13 @@ impl Settings { impl From for Frame { fn from(src: Settings) -> Frame { - Frame::Settings(src) + Frame::Settings(src, AgentProfile::default()) + } +} + +impl From<(Settings, AgentProfile)> for Frame { + fn from(value: (Settings, AgentProfile)) -> Self { + Frame::Settings(value.0, value.1) } } @@ -270,29 +295,32 @@ impl fmt::Debug for Settings { let mut builder = f.debug_struct("Settings"); builder.field("flags", &self.flags); - self.for_each(|setting| match setting { - Setting::EnablePush(v) => { - builder.field("enable_push", &v); - } - Setting::HeaderTableSize(v) => { - builder.field("header_table_size", &v); - } - Setting::InitialWindowSize(v) => { - builder.field("initial_window_size", &v); - } - Setting::MaxConcurrentStreams(v) => { - builder.field("max_concurrent_streams", &v); - } - Setting::MaxFrameSize(v) => { - builder.field("max_frame_size", &v); - } - Setting::MaxHeaderListSize(v) => { - builder.field("max_header_list_size", &v); - } - Setting::EnableConnectProtocol(v) => { - builder.field("enable_connect_protocol", &v); - } - }); + self.for_each( + |setting| match setting { + Setting::EnablePush(v) => { + builder.field("enable_push", &v); + } + Setting::HeaderTableSize(v) => { + builder.field("header_table_size", &v); + } + Setting::InitialWindowSize(v) => { + builder.field("initial_window_size", &v); + } + Setting::MaxConcurrentStreams(v) => { + builder.field("max_concurrent_streams", &v); + } + Setting::MaxFrameSize(v) => { + builder.field("max_frame_size", &v); + } + Setting::MaxHeaderListSize(v) => { + builder.field("max_header_list_size", &v); + } + Setting::EnableConnectProtocol(v) => { + builder.field("enable_connect_protocol", &v); + } + }, + AgentProfile::default(), + ); builder.finish() } diff --git a/src/lib.rs b/src/lib.rs index 3d59ef21e..9f5577730 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ //! [`server::handshake`]: server/fn.handshake.html //! [`client::handshake`]: client/fn.handshake.html +#![doc(html_root_url = "https://docs.rs/h2/0.3.23")] #![deny( missing_debug_implementations, missing_docs, @@ -139,6 +140,8 @@ pub use crate::share::{FlowControl, Ping, PingPong, Pong, RecvStream, SendStream #[cfg(feature = "unstable")] pub use codec::{Codec, SendError, UserError}; +#[allow(missing_docs)] +pub mod profile; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; diff --git a/src/profile.rs b/src/profile.rs new file mode 100644 index 000000000..6128631d0 --- /dev/null +++ b/src/profile.rs @@ -0,0 +1,76 @@ +use crate::frame::{PseudoType, StreamDependency, StreamId}; + +/// This is the default user agent profile used by the library. +/// It can be overridden by setting the `x-client-profile` header. +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum AgentProfile { + Chrome, + Firefox, + Safari, + Edge, + OkHttp, +} + +impl AgentProfile { + /// To pseudo types + pub(crate) fn to_pseudo(&self) -> [PseudoType; 4] { + match self { + AgentProfile::Chrome | AgentProfile::Edge => [ + PseudoType::Method, + PseudoType::Authority, + PseudoType::Scheme, + PseudoType::Path, + ], + AgentProfile::OkHttp => [ + PseudoType::Method, + PseudoType::Path, + PseudoType::Authority, + PseudoType::Scheme, + ], + AgentProfile::Safari => [ + PseudoType::Method, + PseudoType::Scheme, + PseudoType::Path, + PseudoType::Authority, + ], + AgentProfile::Firefox => [ + PseudoType::Method, + PseudoType::Path, + PseudoType::Authority, + PseudoType::Scheme, + ], + } + } + + #[allow(dead_code)] + pub(crate) fn to_stream_dependency(&self) -> StreamDependency { + match self { + AgentProfile::Chrome + | AgentProfile::Edge + | AgentProfile::OkHttp + | AgentProfile::Firefox => StreamDependency::new(StreamId::zero(), 255, true), + AgentProfile::Safari => StreamDependency::new(StreamId::zero(), 254, false), + } + } +} + +/// Default to Chrome +impl Default for AgentProfile { + fn default() -> Self { + AgentProfile::Chrome + } +} + +/// Convert a string to a profile +impl From<&str> for AgentProfile { + fn from(s: &str) -> Self { + match s { + "chrome" => AgentProfile::Chrome, + "firefox" => AgentProfile::Firefox, + "safari" => AgentProfile::Safari, + "edge" => AgentProfile::Edge, + "okhttp" => AgentProfile::OkHttp, + _ => AgentProfile::Chrome, + } + } +} diff --git a/src/proto/connection.rs b/src/proto/connection.rs index 5589fabcb..5349ceff6 100644 --- a/src/proto/connection.rs +++ b/src/proto/connection.rs @@ -510,7 +510,7 @@ where tracing::trace!(?frame, "recv PUSH_PROMISE"); self.streams.recv_push_promise(frame)?; } - Some(Settings(frame)) => { + Some(Settings(frame, _)) => { tracing::trace!(?frame, "recv SETTINGS"); return Ok(ReceivedFrame::Settings(frame)); } diff --git a/src/proto/streams/streams.rs b/src/proto/streams/streams.rs index 132d91bda..8d5532f99 100644 --- a/src/proto/streams/streams.rs +++ b/src/proto/streams/streams.rs @@ -4,6 +4,7 @@ use super::{Buffer, Config, Counts, Prioritized, Recv, Send, Stream, StreamId}; use crate::codec::{Codec, SendError, UserError}; use crate::ext::Protocol; use crate::frame::{self, Frame, Reason}; +use crate::profile::AgentProfile; use crate::proto::{peer, Error, Initiator, Open, Peer, WindowSize}; use crate::{client, proto, server}; @@ -220,6 +221,7 @@ where mut request: Request<()>, end_of_stream: bool, pending: Option<&OpaqueStreamRef>, + profile: AgentProfile, ) -> Result<(StreamRef, bool), SendError> { use super::stream::ContentLength; use http::Method; @@ -273,8 +275,13 @@ where } // Convert the message - let headers = - client::Peer::convert_send_message(stream_id, request, protocol, end_of_stream)?; + let headers = client::Peer::convert_send_message( + stream_id, + request, + protocol, + end_of_stream, + profile, + )?; let mut stream = me.store.insert(stream.id, stream); @@ -1177,7 +1184,12 @@ impl StreamRef { let pushed = { let mut stream = me.store.resolve(self.opaque.key); - let frame = crate::server::Peer::convert_push_message(stream.id, promised_id, request)?; + let frame = crate::server::Peer::convert_push_message( + stream.id, + promised_id, + request, + Default::default(), + )?; actions .send diff --git a/src/server.rs b/src/server.rs index b00bc0866..edd6d627a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -117,6 +117,7 @@ use crate::codec::{Codec, UserError}; use crate::frame::{self, Pseudo, PushPromiseHeaderError, Reason, Settings, StreamId}; +use crate::profile::AgentProfile; use crate::proto::{self, Config, Error, Prioritized}; use crate::{FlowControl, PingPong, RecvStream, SendStream}; @@ -1448,6 +1449,7 @@ impl Peer { stream_id: StreamId, promised_id: StreamId, request: Request<()>, + profile: AgentProfile, ) -> Result { use http::request::Parts; @@ -1479,7 +1481,7 @@ impl Peer { _, ) = request.into_parts(); - let pseudo = Pseudo::request(method, uri, None); + let pseudo = Pseudo::request(method, uri, None, profile); Ok(frame::PushPromise::new( stream_id, diff --git a/tests/h2-fuzz/Cargo.toml b/tests/h2-fuzz/Cargo.toml index b0f9599e9..1833807f9 100644 --- a/tests/h2-fuzz/Cargo.toml +++ b/tests/h2-fuzz/Cargo.toml @@ -6,10 +6,10 @@ license = "MIT" edition = "2018" [dependencies] -h2 = { path = "../.." } +h2-patch = { path = "../.." } env_logger = { version = "0.9", default-features = false } futures = { version = "0.3", default-features = false, features = ["std"] } honggfuzz = "0.5" -http = "1" +http = "0.2" tokio = { version = "1", features = [ "full" ] } diff --git a/tests/h2-fuzz/src/main.rs b/tests/h2-fuzz/src/main.rs index 28905524b..f1069ebe8 100644 --- a/tests/h2-fuzz/src/main.rs +++ b/tests/h2-fuzz/src/main.rs @@ -1,6 +1,7 @@ use futures::future; use futures::stream::FuturesUnordered; use futures::Stream; +use h2_patch as h2; use http::{Method, Request}; use std::future::Future; use std::io; diff --git a/tests/h2-support/Cargo.toml b/tests/h2-support/Cargo.toml index 970648d5a..bc5097218 100644 --- a/tests/h2-support/Cargo.toml +++ b/tests/h2-support/Cargo.toml @@ -6,7 +6,7 @@ publish = false edition = "2018" [dependencies] -h2 = { path = "../..", features = ["stream", "unstable"] } +h2-patch = { path = "../..", features = ["stream", "unstable"] } atty = "0.2" bytes = "1" @@ -14,6 +14,6 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } tracing-tree = "0.2" futures = { version = "0.3", default-features = false } -http = "1" +http = "0.2" tokio = { version = "1", features = ["time"] } tokio-test = "0.4" diff --git a/tests/h2-support/src/assert.rs b/tests/h2-support/src/assert.rs index 88e3d4f7c..07aed7213 100644 --- a/tests/h2-support/src/assert.rs +++ b/tests/h2-support/src/assert.rs @@ -1,3 +1,4 @@ +use h2_patch as h2; #[macro_export] macro_rules! assert_closed { ($transport:expr) => {{ @@ -41,7 +42,7 @@ macro_rules! assert_ping { macro_rules! assert_settings { ($frame:expr) => {{ match $frame { - h2::frame::Frame::Settings(v) => v, + h2::frame::Frame::Settings(v, _) => v, f => panic!("expected SETTINGS; actual={:?}", f), } }}; diff --git a/tests/h2-support/src/client_ext.rs b/tests/h2-support/src/client_ext.rs index eebbae98b..50239e96d 100644 --- a/tests/h2-support/src/client_ext.rs +++ b/tests/h2-support/src/client_ext.rs @@ -1,5 +1,6 @@ use bytes::Buf; use h2::client::{ResponseFuture, SendRequest}; +use h2_patch as h2; use http::Request; /// Extend the `h2::client::SendRequest` type with convenience methods. diff --git a/tests/h2-support/src/frames.rs b/tests/h2-support/src/frames.rs index ba123c0ad..1ecab23dc 100644 --- a/tests/h2-support/src/frames.rs +++ b/tests/h2-support/src/frames.rs @@ -1,3 +1,4 @@ +use h2_patch as h2; use std::convert::TryInto; use std::fmt; @@ -110,7 +111,7 @@ impl Mock { let uri = uri.try_into().unwrap(); let (id, _, fields) = self.into_parts(); let extensions = Default::default(); - let pseudo = frame::Pseudo::request(method, uri, extensions); + let pseudo = frame::Pseudo::request(method, uri, extensions, Default::default()); let frame = frame::Headers::new(id, pseudo, fields); Mock(frame) } @@ -238,7 +239,7 @@ impl Mock { let uri = uri.try_into().unwrap(); let (id, promised, _, fields) = self.into_parts(); let extensions = Default::default(); - let pseudo = frame::Pseudo::request(method, uri, extensions); + let pseudo = frame::Pseudo::request(method, uri, extensions, Default::default()); let frame = frame::PushPromise::new(id, promised, pseudo, fields); Mock(frame) } diff --git a/tests/h2-support/src/lib.rs b/tests/h2-support/src/lib.rs index 3c13c0afe..475d1c728 100644 --- a/tests/h2-support/src/lib.rs +++ b/tests/h2-support/src/lib.rs @@ -1,5 +1,6 @@ //! Utilities to support tests. +use h2_patch as h2; #[macro_use] pub mod assert; diff --git a/tests/h2-support/src/mock.rs b/tests/h2-support/src/mock.rs index 9ec5ba379..071a306c9 100644 --- a/tests/h2-support/src/mock.rs +++ b/tests/h2-support/src/mock.rs @@ -1,4 +1,5 @@ use crate::SendFrame; +use h2_patch as h2; use h2::frame::{self, Frame}; use h2::proto::Error; @@ -187,7 +188,7 @@ impl Handle { let settings = match self.next().await { Some(frame) => match frame.unwrap() { - Frame::Settings(settings) => { + Frame::Settings(settings, _) => { // Send the ACK let ack = frame::Settings::ack(); diff --git a/tests/h2-support/src/prelude.rs b/tests/h2-support/src/prelude.rs index 4338143fd..747ca4b73 100644 --- a/tests/h2-support/src/prelude.rs +++ b/tests/h2-support/src/prelude.rs @@ -1,5 +1,5 @@ // Re-export H2 crate -pub use h2; +pub use h2_patch as h2; pub use h2::client; pub use h2::ext::Protocol; diff --git a/tests/h2-support/src/util.rs b/tests/h2-support/src/util.rs index 02b6450d0..73098f808 100644 --- a/tests/h2-support/src/util.rs +++ b/tests/h2-support/src/util.rs @@ -1,3 +1,5 @@ +use h2_patch as h2; + use bytes::{BufMut, Bytes}; use futures::ready; use std::future::Future; diff --git a/tests/h2-tests/tests/client_request.rs b/tests/h2-tests/tests/client_request.rs index e914d4843..385a59bcf 100644 --- a/tests/h2-tests/tests/client_request.rs +++ b/tests/h2-tests/tests/client_request.rs @@ -240,8 +240,6 @@ async fn request_over_max_concurrent_streams_errors() { // first request is allowed let (resp1, mut stream1) = client.send_request(request, false).unwrap(); - // as long as we let the connection internals tick - client = h2.drive(client.ready()).await.unwrap(); let request = Request::builder() .method(Method::POST) @@ -287,90 +285,6 @@ async fn request_over_max_concurrent_streams_errors() { join(srv, h2).await; } -#[tokio::test] -async fn recv_decrement_max_concurrent_streams_when_requests_queued() { - h2_support::trace_init!(); - let (io, mut srv) = mock::new(); - - let srv = async move { - let settings = srv.assert_client_handshake().await; - assert_default_settings!(settings); - srv.recv_frame( - frames::headers(1) - .request("POST", "https://example.com/") - .eos(), - ) - .await; - srv.send_frame(frames::headers(1).response(200).eos()).await; - - srv.ping_pong([0; 8]).await; - - // limit this server later in life - srv.send_frame(frames::settings().max_concurrent_streams(1)) - .await; - srv.recv_frame(frames::settings_ack()).await; - srv.recv_frame( - frames::headers(3) - .request("POST", "https://example.com/") - .eos(), - ) - .await; - srv.ping_pong([1; 8]).await; - srv.send_frame(frames::headers(3).response(200).eos()).await; - - srv.recv_frame( - frames::headers(5) - .request("POST", "https://example.com/") - .eos(), - ) - .await; - srv.send_frame(frames::headers(5).response(200).eos()).await; - }; - - let h2 = async move { - let (mut client, mut h2) = client::handshake(io).await.expect("handshake"); - // we send a simple req here just to drive the connection so we can - // receive the server settings. - let request = Request::builder() - .method(Method::POST) - .uri("https://example.com/") - .body(()) - .unwrap(); - // first request is allowed - let (response, _) = client.send_request(request, true).unwrap(); - h2.drive(response).await.unwrap(); - - let request = Request::builder() - .method(Method::POST) - .uri("https://example.com/") - .body(()) - .unwrap(); - - // first request is allowed - let (resp1, _) = client.send_request(request, true).unwrap(); - - let request = Request::builder() - .method(Method::POST) - .uri("https://example.com/") - .body(()) - .unwrap(); - - // second request is put into pending_open - let (resp2, _) = client.send_request(request, true).unwrap(); - - h2.drive(async move { - resp1.await.expect("req"); - }) - .await; - join(async move { h2.await.unwrap() }, async move { - resp2.await.unwrap() - }) - .await; - }; - - join(srv, h2).await; -} - #[tokio::test] async fn send_request_poll_ready_when_connection_error() { h2_support::trace_init!(); @@ -423,8 +337,6 @@ async fn send_request_poll_ready_when_connection_error() { // first request is allowed let (resp1, _) = client.send_request(request, true).unwrap(); - // as long as we let the connection internals tick - client = h2.drive(client.ready()).await.unwrap(); let request = Request::builder() .method(Method::POST) @@ -460,7 +372,7 @@ async fn send_request_poll_ready_when_connection_error() { resp2.await.expect_err("req2"); })); - while unordered.next().await.is_some() {} + while let Some(_) = unordered.next().await {} }; join(srv, h2).await; @@ -578,8 +490,9 @@ async fn http_2_request_without_scheme_or_authority() { client .send_request(request, true) .expect_err("should be UserError"); - let _: () = h2.await.expect("h2"); + let ret = h2.await.expect("h2"); drop(client); + ret }; join(srv, h2).await; @@ -702,7 +615,7 @@ async fn connection_close_notifies_response_future() { .0 .await; let err = res.expect_err("response"); - assert_eq!(err.to_string(), "stream closed because of a broken pipe"); + assert_eq!(err.to_string(), "broken pipe"); }; join(async move { conn.await.expect("conn") }, req).await; }; @@ -741,7 +654,7 @@ async fn connection_close_notifies_client_poll_ready() { .0 .await; let err = res.expect_err("response"); - assert_eq!(err.to_string(), "stream closed because of a broken pipe"); + assert_eq!(err.to_string(), "broken pipe"); }; conn.drive(req).await; @@ -749,10 +662,7 @@ async fn connection_close_notifies_client_poll_ready() { let err = poll_fn(move |cx| client.poll_ready(cx)) .await .expect_err("poll_ready"); - assert_eq!( - err.to_string(), - "connection closed because of a broken pipe" - ); + assert_eq!(err.to_string(), "broken pipe"); }; join(srv, client).await; @@ -1591,7 +1501,7 @@ async fn extended_connect_request() { } #[tokio::test] -async fn rogue_server_odd_headers() { +async fn client_builder_header_table_size() { h2_support::trace_init!(); let (io, mut srv) = mock::new(); @@ -1647,36 +1557,28 @@ async fn rogue_server_reused_headers() { srv.recv_frame( frames::headers(1) - .request("GET", "https://camembert.fromage") + .request("GET", "https://example.com/") .eos(), ) .await; srv.send_frame(frames::headers(1).response(200).eos()).await; - srv.send_frame(frames::headers(1)).await; - srv.recv_frame(frames::reset(1).stream_closed()).await; }; - let h2 = async move { - let (mut client, mut h2) = client::handshake(io).await.unwrap(); - - h2.drive(async { - let request = Request::builder() - .method(Method::GET) - .uri("https://camembert.fromage") - .body(()) - .unwrap(); - let _res = client.send_request(request, true).unwrap().0.await.unwrap(); - }) - .await; + let mut builder = client::Builder::new(); + builder.header_table_size(10000); - h2.await.unwrap(); + let h2 = async move { + let (mut client, mut h2) = builder.handshake::<_, Bytes>(io).await.unwrap(); + let request = Request::get("https://example.com/").body(()).unwrap(); + let (response, _) = client.send_request(request, true).unwrap(); + h2.drive(response).await.unwrap(); }; join(srv, h2).await; } #[tokio::test] -async fn client_builder_header_table_size() { +async fn client_builder_header_table_size_1() { h2_support::trace_init!(); let (io, mut srv) = mock::new(); let mut settings = frame::Settings::default(); @@ -1784,7 +1686,7 @@ async fn receive_settings_frame_twice_with_second_one_empty() { srv.read_preface().await.unwrap(); match srv.next().await { Some(frame) => match frame.unwrap() { - h2::frame::Frame::Settings(_) => { + h2::frame::Frame::Settings(_, _) => { let ack = frame::Settings::ack(); srv.send(ack.into()).await.unwrap(); } @@ -1834,7 +1736,7 @@ async fn receive_settings_frame_twice_with_second_one_non_empty() { srv.read_preface().await.unwrap(); match srv.next().await { Some(frame) => match frame.unwrap() { - h2::frame::Frame::Settings(_) => { + h2::frame::Frame::Settings(_, _) => { let ack = frame::Settings::ack(); srv.send(ack.into()).await.unwrap(); } diff --git a/tests/h2-tests/tests/server.rs b/tests/h2-tests/tests/server.rs index c1af54198..91c8d40cd 100644 --- a/tests/h2-tests/tests/server.rs +++ b/tests/h2-tests/tests/server.rs @@ -1255,6 +1255,7 @@ async fn extended_connect_protocol_disabled_by_default() { Method::CONNECT, uri::Uri::from_static("http://bread/baguette"), Protocol::from_static("the-bread-protocol").into(), + Default::default(), ))) .await; @@ -1288,6 +1289,7 @@ async fn extended_connect_protocol_enabled_during_handshake() { Method::CONNECT, uri::Uri::from_static("http://bread/baguette"), Protocol::from_static("the-bread-protocol").into(), + Default::default(), ))) .await; @@ -1335,6 +1337,7 @@ async fn reject_pseudo_protocol_on_non_connect_request() { Method::GET, uri::Uri::from_static("http://bread/baguette"), Some(Protocol::from_static("the-bread-protocol")), + Default::default(), ))) .await;