From 8e21a74fd1f6a18a3663d3326c12a82e8bb794cb Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 15 Nov 2024 21:09:56 +0800 Subject: [PATCH] Merge patch (#16) * v0.3.26 * Rename project to `rh2` * Refactor frame sending custom implementation * Export frame `PseudoOrder` settings * Reduce unnecessary Option packaging * v0.3.27 * fix(frame/headers): Fix error when headers priority is empty * v0.3.29 * feat(frame/headers): Packaging headers pseudo order type (#8) * feat(frame/settings): Packaging settings type (#9) * Initialize frame settings order in advance * v0.3.31 * feat(frame): Add unknown_setting frame settings (#10) * Add unknown_setting patch * Customize all Http Settings order * v0.3.40 * fix(frame): Fix unknown setting encode (#11) * v0.3.41 * feat: Replace with static settings (#12) * v0.3.50 * feat: Destructive update, fixed-length array records the setting frame order (#13) * v0.3.60 * Update README.md * Sync upstream (#14) * fix: streams awaiting capacity lockout (#730) (#734) This PR changes the the assign-capacity queue to prioritize streams that are send-ready. This is necessary to prevent a lockout when streams aren't able to proceed while waiting for connection capacity, but there is none. Closes https://github.com/hyperium/hyper/issues/3338 Co-authored-by: dswij * v0.3.23 * streams: limit error resets for misbehaving connections This change causes GOAWAYs to be issued to misbehaving connections which for one reason or another cause us to emit lots of error resets. Error resets are not generally expected from valid implementations anyways. The threshold after which we issue GOAWAYs is tunable, and will default to 1024. * Prepare v0.3.24 * perf: optimize header list size calculations (#750) This speeds up loading blocks in cases where we have many headers already. * v0.3.25 * refactor: cleanup new unused warnings (#757) * fix: limit number of CONTINUATION frames allowed Calculate the amount of allowed CONTINUATION frames based on other settings. max_header_list_size / max_frame_size That is about how many CONTINUATION frames would be needed to send headers up to the max allowed size. We then multiply by that by a small amount, to allow for implementations that don't perfectly pack into the minimum frames *needed*. In practice, *much* more than that would be a very inefficient peer, or a peer trying to waste resources. See https://seanmonstar.com/blog/hyper-http2-continuation-flood/ for more info. * v0.3.26 * fix: return a WriteZero error if frames cannot be written (#783) Some operating systems will allow you continually call `write()` on a closed socket, and will return `Ok(0)` instead of an error. This patch checks for a zero write, and instead of looping forever trying to write, returns a proper error. Closes #781 Co-authored-by: leibeiyi * lints: fix unexpected cfgs warnings * ci: pin deps for MSRV * ci: pin more deps for MSRV job (#817) * fix: notify_recv after send_reset() in reset_on_recv_stream_err() to ensure local stream is released properly (#816) Similar to what have been done in fn send_reset(), we should notify RecvStream that is parked after send_reset(). Co-authored-by: Jiahao Liang --------- Co-authored-by: Sean McArthur Co-authored-by: dswij Co-authored-by: Noah Kennedy Co-authored-by: beiyi lei Co-authored-by: leibeiyi Co-authored-by: Jiahao Liang * v0.3.61 --------- Co-authored-by: Sean McArthur Co-authored-by: dswij Co-authored-by: Noah Kennedy Co-authored-by: beiyi lei Co-authored-by: leibeiyi Co-authored-by: Jiahao Liang --- CHANGELOG.md | 20 +--- README.md | 20 ++-- src/client.rs | 31 ++++-- src/codec/framed_write.rs | 8 +- src/frame/headers.rs | 79 ++++++++----- src/frame/mod.rs | 3 +- src/frame/settings.rs | 146 ++++++++++++++++++------- src/proto/connection.rs | 4 +- src/proto/streams/streams.rs | 10 +- tests/h2-tests/tests/prioritization.rs | 1 + tests/h2-tests/tests/server.rs | 1 + tests/h2-tests/tests/stream_states.rs | 2 +- 12 files changed, 215 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91f571fe5..06f37bee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,25 +14,9 @@ * Limit number of CONTINUATION frames for misbehaving connections. -# 0.4.3 (March 15, 2024) +# 0.3.25 (March 15, 2024) -* Fix flow control limits to not apply until receiving SETTINGS ack. -* Fix not returning an error if IO ended without `close_notify`. -* Improve performance of decoding many headers. - -# 0.4.2 (January 17th, 2024) - -* Limit error resets for misbehaving connections. -* Fix selecting MAX_CONCURRENT_STREAMS value if no value is advertised initially. - -# 0.4.1 (January 8, 2024) - -* Fix assigning connection capacity which could starve streams in some instances. - -# 0.4.0 (November 15, 2023) - -* Update to `http` 1.0. -* Remove deprecated `Server::poll_close()`. +* Improve performance decoding many headers. # 0.3.24 (January 17, 2024) diff --git a/README.md b/README.md index cd2831d19..310110ba4 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,14 @@ # H2 -This project is forked from [h2](https://github.com/hyperium/h2) - A Tokio aware, HTTP/2 client & server implementation for Rust. [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) -[![Crates.io](https://img.shields.io/crates/v/h2.svg)](https://crates.io/crates/h2) -[![Documentation](https://docs.rs/h2/badge.svg)][dox] +[![Crates.io](https://img.shields.io/crates/v/rh2.svg)](https://crates.io/crates/rh2) +[![Documentation](https://docs.rs/rh2/badge.svg)][dox] More information about this crate can be found in the [crate documentation][dox]. -[dox]: https://docs.rs/h2 +[dox]: https://docs.rs/rh2 ## Features @@ -34,19 +32,19 @@ This crate is now used by [hyper](https://github.com/hyperium/hyper), which will ## Usage -To use `h2`, first add this to your `Cargo.toml`: +To use `rh2`, first add this to your `Cargo.toml`: ```toml [dependencies] -h2 = "0.4" +rh2 = "0.4" ``` Next, add this to your crate: ```rust -extern crate h2; +extern crate rh2; -use h2::server::Connection; +use rh2::server::Connection; fn main() { // ... @@ -71,3 +69,7 @@ actively maintained. [solicit]: https://github.com/mlalic/solicit [rust-http2]: https://github.com/stepancheg/rust-http2 [h2spec]: https://github.com/summerwind/h2spec + +## Accolades + +The project is based on a fork of [h2](https://github.com/hyperium/h2). diff --git a/src/client.rs b/src/client.rs index a8286e83b..a0cf24b04 100644 --- a/src/client.rs +++ b/src/client.rs @@ -138,7 +138,8 @@ use crate::codec::{Codec, SendError, UserError}; use crate::ext::Protocol; use crate::frame::{ - Headers, Pseudo, PseudoOrder, Reason, Settings, SettingsOrder, StreamDependency, StreamId, + Headers, Pseudo, PseudoOrder, PseudoOrders, Reason, Settings, SettingsOrder, StreamDependency, + StreamId }; use crate::proto::{self, Error}; use crate::{FlowControl, PingPong, RecvStream, SendStream}; @@ -347,7 +348,7 @@ pub struct Builder { local_max_error_reset_streams: Option, /// The headers frame pseudo order - headers_pseudo_order: Option<[PseudoOrder; 4]>, + headers_pseudo_order: Option, /// The headers frame priority headers_priority: Option, @@ -677,20 +678,20 @@ impl Builder { } /// Set http2 header pseudo order - pub fn headers_psuedo(&mut self, headers_psuedo: Option<[PseudoOrder; 4]>) -> &mut Self { - self.headers_pseudo_order = headers_psuedo; + pub fn headers_psuedo(&mut self, order: [PseudoOrder; 4]) -> &mut Self { + self.headers_pseudo_order = Some(order.into()); self } /// Set http2 header priority - pub fn headers_priority(&mut self, headers_priority: Option) -> &mut Self { - self.headers_priority = headers_priority; + pub fn headers_priority(&mut self, headers_priority: StreamDependency) -> &mut Self { + self.headers_priority = Some(headers_priority); self } /// Settings frame order - pub fn settings_order(&mut self, order: Option<[SettingsOrder; 2]>) -> &mut Self { - self.settings.set_settings_order(order); + pub fn settings_order(&mut self, order: [SettingsOrder; 8]) -> &mut Self { + self.settings.set_settings_order(Some(order)); self } @@ -1173,6 +1174,18 @@ impl Builder { self } + /// unknown_setting8 + pub fn unknown_setting8(&mut self, enabled: bool) -> &mut Self { + self.settings.set_unknown_setting_8(enabled); + self + } + + /// unknown_setting8 + pub fn unknown_setting9(&mut self, enabled: bool) -> &mut Self { + self.settings.set_unknown_setting_9(enabled); + self + } + /// Sets the first stream ID to something other than 1. #[cfg(feature = "unstable")] pub fn initial_stream_id(&mut self, stream_id: u32) -> &mut Self { @@ -1614,7 +1627,7 @@ impl Peer { request: Request<()>, protocol: Option, end_of_stream: bool, - pseudo_order: Option<[PseudoOrder; 4]>, + pseudo_order: Option, headers_priority: Option, ) -> Result { use http::request::Parts; diff --git a/src/codec/framed_write.rs b/src/codec/framed_write.rs index c88af02da..a94647bce 100644 --- a/src/codec/framed_write.rs +++ b/src/codec/framed_write.rs @@ -133,7 +133,7 @@ where loop { while !self.encoder.is_empty() { - match self.encoder.next { + let n = match self.encoder.next { Some(Next::Data(ref mut frame)) => { tracing::trace!(queued_data_frame = true); let mut buf = (&mut self.encoder.buf).chain(frame.payload_mut()); @@ -148,6 +148,12 @@ where ))? } }; + if n == 0 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write frame to socket", + ))); + } } match self.encoder.unset_frame() { diff --git a/src/frame/headers.rs b/src/frame/headers.rs index cf2ac5145..af50c6224 100644 --- a/src/frame/headers.rs +++ b/src/frame/headers.rs @@ -74,7 +74,7 @@ pub struct Pseudo { pub status: Option, // order of pseudo headers - pub order: Option<[PseudoOrder; 4]>, + pub order: PseudoOrders, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -85,6 +85,26 @@ pub enum PseudoOrder { Path, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PseudoOrders([PseudoOrder; 4]); + +impl From<[PseudoOrder; 4]> for PseudoOrders { + fn from(src: [PseudoOrder; 4]) -> Self { + PseudoOrders(src) + } +} + +impl Default for PseudoOrders { + fn default() -> Self { + PseudoOrders([ + PseudoOrder::Method, + PseudoOrder::Scheme, + PseudoOrder::Authority, + PseudoOrder::Path, + ]) + } +} + #[derive(Debug)] pub struct Iter { /// Pseudo headers @@ -131,6 +151,13 @@ impl Headers { fields: HeaderMap, stream_dep: Option, ) -> Self { + // If the stream dependency is set, the PRIORITY flag must be set + let flags = if stream_dep.is_some() { + HeadersFlag(END_HEADERS | PRIORITY) + } else { + HeadersFlag::default() + }; + Headers { stream_id, stream_dep, @@ -140,7 +167,7 @@ impl Headers { is_over_size: false, pseudo, }, - flags: HeadersFlag::default(), + flags, } } @@ -580,7 +607,7 @@ impl Pseudo { method: Method, uri: Uri, protocol: Option, - pseudo_order: Option<[PseudoOrder; 4]>, + order: Option, ) -> Self { let parts = uri::Parts::from(uri); @@ -610,7 +637,7 @@ impl Pseudo { path, protocol, status: None, - order: pseudo_order, + order: order.unwrap_or_default(), }; // If the URI includes a scheme component, add it to the pseudo headers @@ -635,7 +662,7 @@ impl Pseudo { path: None, protocol: None, status: Some(status), - order: None, + order: Default::default(), } } @@ -730,28 +757,26 @@ impl Iterator for Iter { use crate::hpack::Header::*; if let Some(ref mut pseudo) = self.pseudo { - if let Some(orders) = pseudo.order.as_ref() { - for pseudo_type in orders { - match pseudo_type { - PseudoOrder::Method => { - if let Some(method) = pseudo.method.take() { - return Some(Method(method)); - } + for pseudo_type in pseudo.order.0.iter() { + match pseudo_type { + PseudoOrder::Method => { + if let Some(method) = pseudo.method.take() { + return Some(Method(method)); } - PseudoOrder::Scheme => { - if let Some(scheme) = pseudo.scheme.take() { - return Some(Scheme(scheme)); - } + } + PseudoOrder::Scheme => { + if let Some(scheme) = pseudo.scheme.take() { + return Some(Scheme(scheme)); } - PseudoOrder::Authority => { - if let Some(authority) = pseudo.authority.take() { - return Some(Authority(authority)); - } + } + PseudoOrder::Authority => { + if let Some(authority) = pseudo.authority.take() { + return Some(Authority(authority)); } - PseudoOrder::Path => { - if let Some(path) = pseudo.path.take() { - return Some(Path(path)); - } + } + PseudoOrder::Path => { + if let Some(path) = pseudo.path.take() { + return Some(Path(path)); } } } @@ -827,9 +852,9 @@ impl HeadersFlag { } impl Default for HeadersFlag { - /// Returns a `HeadersFlag` value with `END_HEADERS` and `PRIORITY` set. + /// Returns a `HeadersFlag` value with `END_HEADERS` set. fn default() -> Self { - HeadersFlag(END_HEADERS | PRIORITY) + HeadersFlag(END_HEADERS) } } @@ -1048,6 +1073,8 @@ fn decoded_header_size(name: usize, value: usize) -> usize { #[cfg(test)] mod test { + use std::iter::FromIterator; + use super::*; use crate::frame; use crate::hpack::{huffman, Encoder}; diff --git a/src/frame/mod.rs b/src/frame/mod.rs index b08054149..b45592721 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -53,7 +53,8 @@ pub use self::go_away::GoAway; pub use self::head::{Head, Kind}; pub(crate) use self::headers::PseudoOrder; pub use self::headers::{ - parse_u64, Continuation, Headers, Pseudo, PushPromise, PushPromiseHeaderError, + parse_u64, Continuation, Headers, Pseudo, PseudoOrder, PseudoOrders, PushPromise, + PushPromiseHeaderError, }; pub use self::ping::Ping; pub use self::priority::{Priority, StreamDependency}; diff --git a/src/frame/settings.rs b/src/frame/settings.rs index ff617d977..ec4ba3c52 100644 --- a/src/frame/settings.rs +++ b/src/frame/settings.rs @@ -5,8 +5,38 @@ use bytes::{BufMut, BytesMut}; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum SettingsOrder { + HeaderTableSize, + EnablePush, InitialWindowSize, MaxConcurrentStreams, + MaxFrameSize, + MaxHeaderListSize, + UnknownSetting8, + UnknownSetting9, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SettingsOrders([SettingsOrder; 8]); + +impl From<[SettingsOrder; 8]> for SettingsOrders { + fn from(order: [SettingsOrder; 8]) -> Self { + SettingsOrders(order) + } +} + +impl Default for SettingsOrders { + fn default() -> Self { + SettingsOrders([ + SettingsOrder::HeaderTableSize, + SettingsOrder::EnablePush, + SettingsOrder::InitialWindowSize, + SettingsOrder::MaxConcurrentStreams, + SettingsOrder::MaxFrameSize, + SettingsOrder::MaxHeaderListSize, + SettingsOrder::UnknownSetting8, + SettingsOrder::UnknownSetting9, + ]) + } } #[derive(Clone, Default, Eq, PartialEq)] @@ -20,8 +50,10 @@ pub struct Settings { max_frame_size: Option, max_header_list_size: Option, enable_connect_protocol: Option, + unknown_setting_8: Option, + unknown_setting_9: Option, // Fields for the settings frame order - settings_order: Option<[SettingsOrder; 2]>, + settings_orders: SettingsOrders, } /// An enum that lists all valid settings that can be sent in a SETTINGS @@ -37,6 +69,8 @@ pub enum Setting { MaxFrameSize(u32), MaxHeaderListSize(u32), EnableConnectProtocol(u32), + UnknownSetting8(u32), + UnknownSetting9(u32), } #[derive(Copy, Clone, Eq, PartialEq, Default)] @@ -133,8 +167,16 @@ impl Settings { self.header_table_size = size; } - pub fn set_settings_order(&mut self, order: Option<[SettingsOrder; 2]>) { - self.settings_order = order; + pub fn set_unknown_setting_8(&mut self, enable: bool) { + self.unknown_setting_8 = Some(enable as u32); + } + + pub fn set_unknown_setting_9(&mut self, enable: bool) { + self.unknown_setting_9 = Some(enable as u32); + } + + pub fn set_settings_order(&mut self, order: Option<[SettingsOrder; 8]>) { + self.settings_orders = order.map_or(SettingsOrders::default(), SettingsOrders::from); } pub fn load(head: Head, payload: &[u8]) -> Result { @@ -209,6 +251,22 @@ impl Settings { return Err(Error::InvalidSettingValue); } }, + Some(UnknownSetting8(val)) => match val { + 0 | 1 => { + settings.unknown_setting_8 = Some(val); + } + _ => { + return Err(Error::InvalidSettingValue); + } + }, + Some(UnknownSetting9(val)) => match val { + 0 | 1 => { + settings.unknown_setting_9 = Some(val); + } + _ => { + return Err(Error::InvalidSettingValue); + } + }, None => {} } } @@ -241,47 +299,51 @@ impl Settings { fn for_each(&self, mut f: F) { use self::Setting::*; - if let Some(v) = self.header_table_size { - f(HeaderTableSize(v)); - } - - if let Some(v) = self.enable_push { - f(EnablePush(v)); - } - - if let Some(settings_frame_order) = self.settings_order { - for order in settings_frame_order { - match order { - SettingsOrder::InitialWindowSize => { - if let Some(v) = self.initial_window_size { - f(InitialWindowSize(v)); - } + for order in self.settings_orders.0.iter() { + match order { + SettingsOrder::HeaderTableSize => { + if let Some(v) = self.header_table_size { + f(HeaderTableSize(v)); } - SettingsOrder::MaxConcurrentStreams => { - if let Some(v) = self.max_concurrent_streams { - f(MaxConcurrentStreams(v)); - } + } + SettingsOrder::EnablePush => { + if let Some(v) = self.enable_push { + f(EnablePush(v)); + } + } + SettingsOrder::InitialWindowSize => { + if let Some(v) = self.initial_window_size { + f(InitialWindowSize(v)); + } + } + SettingsOrder::MaxConcurrentStreams => { + if let Some(v) = self.max_concurrent_streams { + f(MaxConcurrentStreams(v)); + } + } + SettingsOrder::UnknownSetting8 => { + if let Some(v) = self.unknown_setting_8 { + f(UnknownSetting8(v)); + } + } + SettingsOrder::UnknownSetting9 => { + if let Some(v) = self.unknown_setting_9 { + f(UnknownSetting9(v)); + } + } + SettingsOrder::MaxFrameSize => { + if let Some(v) = self.max_frame_size { + f(MaxFrameSize(v)); + } + } + SettingsOrder::MaxHeaderListSize => { + if let Some(v) = self.max_header_list_size { + f(MaxHeaderListSize(v)); } } - } - } else { - if let Some(v) = self.initial_window_size { - f(InitialWindowSize(v)); - } - - if let Some(v) = self.max_concurrent_streams { - f(MaxConcurrentStreams(v)); } } - if let Some(v) = self.max_frame_size { - f(MaxFrameSize(v)); - } - - if let Some(v) = self.max_header_list_size { - f(MaxHeaderListSize(v)); - } - if let Some(v) = self.enable_connect_protocol { f(EnableConnectProtocol(v)); } @@ -321,6 +383,12 @@ impl fmt::Debug for Settings { Setting::EnableConnectProtocol(v) => { builder.field("enable_connect_protocol", &v); } + Setting::UnknownSetting8(v) => { + builder.field("unknown_setting8", &v); + } + Setting::UnknownSetting9(v) => { + builder.field("unknown_setting9", &v); + } }); builder.finish() @@ -376,6 +444,8 @@ impl Setting { MaxFrameSize(v) => (5, v), MaxHeaderListSize(v) => (6, v), EnableConnectProtocol(v) => (8, v), + UnknownSetting8(v) => (8, v), + UnknownSetting9(v) => (9, v), }; dst.put_u16(kind); diff --git a/src/proto/connection.rs b/src/proto/connection.rs index 668de490d..cc97ca4ab 100644 --- a/src/proto/connection.rs +++ b/src/proto/connection.rs @@ -6,7 +6,7 @@ use crate::frame::DEFAULT_INITIAL_WINDOW_SIZE; use crate::proto::*; use bytes::Bytes; -use frame::{PseudoOrder, StreamDependency}; +use frame::{PseudoOrders, StreamDependency}; use futures_core::Stream; use std::io; use std::marker::PhantomData; @@ -84,7 +84,7 @@ pub(crate) struct Config { pub remote_reset_stream_max: usize, pub local_error_reset_streams_max: Option, pub settings: frame::Settings, - pub headers_pseudo_order: Option<[PseudoOrder; 4]>, + pub headers_pseudo_order: Option, pub headers_priority: Option, } diff --git a/src/proto/streams/streams.rs b/src/proto/streams/streams.rs index e5d9fbb58..e632b7742 100644 --- a/src/proto/streams/streams.rs +++ b/src/proto/streams/streams.rs @@ -1,4 +1,4 @@ -use super::frame::{PseudoOrder, StreamDependency}; +use super::frame::{PseudoOrders, StreamDependency}; use super::recv::RecvHeaderBlockError; use super::store::{self, Entry, Resolve, Store}; use super::{Buffer, Config, Counts, Prioritized, Recv, Send, Stream, StreamId}; @@ -36,7 +36,7 @@ where send_buffer: Arc>, /// Headers frame pseudo order - headers_pseudo_order: Option<[PseudoOrder; 4]>, + headers_pseudo_order: Option, /// Headers frame priority headers_priority: Option, @@ -118,7 +118,7 @@ where pub fn new( config: Config, headers_priority: Option, - headers_pseudo_order: Option<[PseudoOrder; 4]>, + headers_pseudo_order: Option, ) -> Self { let peer = P::r#dyn(); @@ -1062,8 +1062,8 @@ where Streams { inner: self.inner.clone(), send_buffer: self.send_buffer.clone(), - headers_priority: self.headers_priority.clone(), - headers_pseudo_order: self.headers_pseudo_order.clone(), + headers_priority: self.headers_priority, + headers_pseudo_order: self.headers_pseudo_order, _p: ::std::marker::PhantomData, } } diff --git a/tests/h2-tests/tests/prioritization.rs b/tests/h2-tests/tests/prioritization.rs index dd4ed9fea..11d2c2ccf 100644 --- a/tests/h2-tests/tests/prioritization.rs +++ b/tests/h2-tests/tests/prioritization.rs @@ -1,3 +1,4 @@ +use futures::future::{join, select}; use futures::{pin_mut, FutureExt, StreamExt}; use h2_support::prelude::*; diff --git a/tests/h2-tests/tests/server.rs b/tests/h2-tests/tests/server.rs index 91c8d40cd..919164060 100644 --- a/tests/h2-tests/tests/server.rs +++ b/tests/h2-tests/tests/server.rs @@ -1,5 +1,6 @@ #![deny(warnings)] +use futures::future::join; use futures::StreamExt; use h2_support::prelude::*; use tokio::io::AsyncWriteExt; diff --git a/tests/h2-tests/tests/stream_states.rs b/tests/h2-tests/tests/stream_states.rs index facd367e8..d511f92a3 100644 --- a/tests/h2-tests/tests/stream_states.rs +++ b/tests/h2-tests/tests/stream_states.rs @@ -1,6 +1,6 @@ #![deny(warnings)] -use futures::future::lazy; +use futures::future::{join, join3, lazy, try_join}; use futures::{FutureExt, StreamExt, TryStreamExt}; use h2_support::prelude::*; use h2_support::util::yield_once;