From 1bdda7686700c61cdcdfa362d98183c6fa0f4a44 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 15 Dec 2023 13:17:06 -0800 Subject: [PATCH 1/2] support custom settings --- src/client.rs | 20 ++++++++++++++++++++ src/frame/settings.rs | 28 +++++++++++++++++++++++++++- src/proto/connection.rs | 11 +++++++++++ src/proto/streams/send.rs | 10 ++++++++++ src/proto/streams/streams.rs | 4 ++++ src/server.rs | 35 +++++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index ffeda607..ca911741 100644 --- a/src/client.rs +++ b/src/client.rs @@ -557,6 +557,16 @@ where pub fn current_max_recv_streams(&self) -> usize { self.inner.current_max_recv_streams() } + + /// Returns the value of a custom setting specified in the [SETTINGS frame][1]. + /// + /// This method returns the currently acknowledged value received from the + /// remote. + /// + /// [1]: https://httpwg.org/specs/rfc7540.html#rfc.section.6.5 + pub fn custom_setting(&self, id: u16) -> Option { + self.inner.custom_setting(id) + } } impl fmt::Debug for SendRequest @@ -859,6 +869,16 @@ impl Builder { self } + /// Set a custom setting in the SETTINGS frame. + /// + /// See [Section 6.5] in the HTTP/2 spec for more details. + /// + /// [Section 6.5]: https://httpwg.org/specs/rfc7540.html#rfc.section.6.5 + pub fn custom_setting(&mut self, id: u16, value: u32) -> &mut Self { + self.settings.set_custom_setting(id, Some(value)); + self + } + /// Sets the initial maximum of locally initiated (send) streams. /// /// The initial settings will be overwritten by the remote peer when diff --git a/src/frame/settings.rs b/src/frame/settings.rs index 484498a9..90fd7b10 100644 --- a/src/frame/settings.rs +++ b/src/frame/settings.rs @@ -2,6 +2,7 @@ use std::fmt; use crate::frame::{util, Error, Frame, FrameSize, Head, Kind, StreamId}; use bytes::{BufMut, BytesMut}; +use std::collections::BTreeMap; #[derive(Clone, Default, Eq, PartialEq)] pub struct Settings { @@ -14,6 +15,7 @@ pub struct Settings { max_frame_size: Option, max_header_list_size: Option, enable_connect_protocol: Option, + custom: BTreeMap, } /// An enum that lists all valid settings that can be sent in a SETTINGS @@ -29,6 +31,7 @@ pub enum Setting { MaxFrameSize(u32), MaxHeaderListSize(u32), EnableConnectProtocol(u32), + Custom(u16, u32), } #[derive(Copy, Clone, Eq, PartialEq, Default)] @@ -125,6 +128,18 @@ impl Settings { self.header_table_size = size; } + pub fn set_custom_setting(&mut self, id: u16, val: Option) { + if let Some(val) = val { + self.custom.insert(id, val); + } else { + self.custom.remove(&id); + } + } + + pub fn custom_settings(&self) -> &BTreeMap { + &self.custom + } + pub fn load(head: Head, payload: &[u8]) -> Result { use self::Setting::*; @@ -197,6 +212,9 @@ impl Settings { return Err(Error::InvalidSettingValue); } }, + Some(Custom(id, val)) => { + settings.custom.insert(id, val); + } None => {} } } @@ -256,6 +274,10 @@ impl Settings { if let Some(v) = self.enable_connect_protocol { f(EnableConnectProtocol(v)); } + + for (id, v) in &self.custom { + f(Custom(*id, *v)); + } } } @@ -292,6 +314,9 @@ impl fmt::Debug for Settings { Setting::EnableConnectProtocol(v) => { builder.field("enable_connect_protocol", &v); } + Setting::Custom(id, v) => { + builder.field(format!("0x{:04x}", id).as_str(), &v); + } }); builder.finish() @@ -315,7 +340,7 @@ impl Setting { 5 => Some(MaxFrameSize(val)), 6 => Some(MaxHeaderListSize(val)), 8 => Some(EnableConnectProtocol(val)), - _ => None, + id => Some(Custom(id, val)), } } @@ -347,6 +372,7 @@ impl Setting { MaxFrameSize(v) => (5, v), MaxHeaderListSize(v) => (6, v), EnableConnectProtocol(v) => (8, v), + Custom(id, v) => (id, v), }; dst.put_u16(kind); diff --git a/src/proto/connection.rs b/src/proto/connection.rs index 5589fabc..b0384c9a 100644 --- a/src/proto/connection.rs +++ b/src/proto/connection.rs @@ -162,6 +162,13 @@ where self.inner.settings.send_settings(settings) } + /// Send a new SETTINGS frame with a custom setting. + pub(crate) fn set_custom_setting(&mut self, id: u16, value: u32) -> Result<(), UserError> { + let mut settings = frame::Settings::default(); + settings.set_custom_setting(id, Some(value)); + self.inner.settings.send_settings(settings) + } + /// Returns the maximum number of concurrent streams that may be initiated /// by this peer. pub(crate) fn max_send_streams(&self) -> usize { @@ -362,6 +369,10 @@ where fn clear_expired_reset_streams(&mut self) { self.inner.streams.clear_expired_reset_streams(); } + + pub(crate) fn custom_setting(&self, id: u16) -> Option { + self.inner.streams.custom_setting(id) + } } impl ConnectionInner diff --git a/src/proto/streams/send.rs b/src/proto/streams/send.rs index 2a7abba0..8d729e8f 100644 --- a/src/proto/streams/send.rs +++ b/src/proto/streams/send.rs @@ -10,6 +10,7 @@ use bytes::Buf; use tokio::io::AsyncWrite; use std::cmp::Ordering; +use std::collections::BTreeMap; use std::io; use std::task::{Context, Poll, Waker}; @@ -38,6 +39,9 @@ pub(super) struct Send { /// If extended connect protocol is enabled. is_extended_connect_protocol_enabled: bool, + + /// Custom settings + custom_settings: BTreeMap, } /// A value to detect which public API has called `poll_reset`. @@ -57,6 +61,7 @@ impl Send { prioritize: Prioritize::new(config), is_push_enabled: true, is_extended_connect_protocol_enabled: false, + custom_settings: BTreeMap::new(), } } @@ -434,6 +439,7 @@ impl Send { if let Some(val) = settings.is_extended_connect_protocol_enabled() { self.is_extended_connect_protocol_enabled = val; } + self.custom_settings.extend(settings.custom_settings()); // Applies an update to the remote endpoint's initial window size. // @@ -582,4 +588,8 @@ impl Send { pub(crate) fn is_extended_connect_protocol_enabled(&self) -> bool { self.is_extended_connect_protocol_enabled } + + pub(crate) fn custom_setting(&self, id: u16) -> Option { + self.custom_settings.get(&id).map(|v| *v) + } } diff --git a/src/proto/streams/streams.rs b/src/proto/streams/streams.rs index 875b6103..a63163d9 100644 --- a/src/proto/streams/streams.rs +++ b/src/proto/streams/streams.rs @@ -330,6 +330,10 @@ where let me = self.inner.lock().unwrap(); me.counts.max_recv_streams() } + + pub(crate) fn custom_setting(&self, id: u16) -> Option { + self.inner.lock().unwrap().actions.send.custom_setting(id) + } } impl DynStreams<'_, B> { diff --git a/src/server.rs b/src/server.rs index b00bc086..e65e54df 100644 --- a/src/server.rs +++ b/src/server.rs @@ -497,6 +497,21 @@ where Ok(()) } + /// Set a custom setting in the SETTINGS frame. + /// + /// See [Section 6.5] in the HTTP/2 spec for more details. + /// + /// [Section 6.5]: https://httpwg.org/specs/rfc7540.html#rfc.section.6.5 + /// + /// # Errors + /// + /// Returns an error if a previous call is still pending acknowledgement + /// from the remote endpoint. + pub fn set_custom_setting(&mut self, id: u16, value: u32) -> Result<(), crate::Error> { + self.connection.set_custom_setting(id, value)?; + Ok(()) + } + /// Returns `Ready` when the underlying connection has closed. /// /// If any new inbound streams are received during a call to `poll_closed`, @@ -587,6 +602,16 @@ where pub fn num_wired_streams(&self) -> usize { self.connection.num_wired_streams() } + + /// Returns the value of a custom setting specified in the [SETTINGS frame][1]. + /// + /// This method returns the currently acknowledged value received from the + /// remote. + /// + /// [1]: https://httpwg.org/specs/rfc7540.html#rfc.section.6.5 + pub fn custom_setting(&self, id: u16) -> Option { + self.connection.custom_setting(id) + } } #[cfg(feature = "stream")] @@ -1023,6 +1048,16 @@ impl Builder { self } + /// Set a custom setting in the SETTINGS frame. + /// + /// See [Section 6.5] in the HTTP/2 spec for more details. + /// + /// [Section 6.5]: https://httpwg.org/specs/rfc7540.html#rfc.section.6.5 + pub fn set_custom_setting(&mut self, id: u16, value: Option) -> &mut Self { + self.settings.set_custom_setting(id, value); + self + } + /// Creates a new configured HTTP/2 server backed by `io`. /// /// It is expected that `io` already be in an appropriate state to commence From d8058d27ecd3a23561bab7afa0486ecf753caae0 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 15 Dec 2023 13:28:31 -0800 Subject: [PATCH 2/2] limit custom settings --- src/client.rs | 16 ++++++++++++++++ src/frame/settings.rs | 4 ++-- src/proto/connection.rs | 3 +++ src/proto/streams/mod.rs | 4 ++++ src/proto/streams/send.rs | 12 +++++++++--- src/server.rs | 17 ++++++++++++++++- 6 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/client.rs b/src/client.rs index ca911741..d3c43d28 100644 --- a/src/client.rs +++ b/src/client.rs @@ -143,6 +143,7 @@ use crate::{FlowControl, PingPong, RecvStream, SendStream}; use bytes::{Buf, Bytes}; use http::{uri, HeaderMap, Method, Request, Response, Version}; +use std::collections::BTreeSet; use std::fmt; use std::future::Future; use std::pin::Pin; @@ -343,6 +344,9 @@ pub struct Builder { /// /// When this gets exceeded, we issue GOAWAYs. local_max_error_reset_streams: Option, + + /// Custom settings IDs to be tracked from the remote + allowed_custom_settings: BTreeSet, } #[derive(Debug)] @@ -673,6 +677,7 @@ impl Builder { settings: Default::default(), stream_id: 1.into(), local_max_error_reset_streams: Some(proto::DEFAULT_LOCAL_RESET_COUNT_MAX), + allowed_custom_settings: BTreeSet::new(), } } @@ -879,6 +884,16 @@ impl Builder { self } + /// By default, unknown settings recieved from the remote will be ignored. + /// + /// See [Section 6.5] in the HTTP/2 spec for more details. + /// + /// [Section 6.5]: https://httpwg.org/specs/rfc7540.html#rfc.section.6.5 + pub fn allow_custom_setting(&mut self, id: u16) -> &mut Self { + self.allowed_custom_settings.insert(id); + self + } + /// Sets the initial maximum of locally initiated (send) streams. /// /// The initial settings will be overwritten by the remote peer when @@ -1355,6 +1370,7 @@ where remote_reset_stream_max: builder.pending_accept_reset_stream_max, local_error_reset_streams_max: builder.local_max_error_reset_streams, settings: builder.settings.clone(), + allowed_custom_settings: builder.allowed_custom_settings, }, ); let send_request = SendRequest { diff --git a/src/frame/settings.rs b/src/frame/settings.rs index 90fd7b10..76fbe76b 100644 --- a/src/frame/settings.rs +++ b/src/frame/settings.rs @@ -136,8 +136,8 @@ impl Settings { } } - pub fn custom_settings(&self) -> &BTreeMap { - &self.custom + pub fn custom_setting(&self, id: u16) -> Option { + self.custom.get(&id).copied() } pub fn load(head: Head, payload: &[u8]) -> Result { diff --git a/src/proto/connection.rs b/src/proto/connection.rs index b0384c9a..461b92cd 100644 --- a/src/proto/connection.rs +++ b/src/proto/connection.rs @@ -7,6 +7,7 @@ use crate::proto::*; use bytes::Bytes; use futures_core::Stream; +use std::collections::BTreeSet; use std::io; use std::marker::PhantomData; use std::pin::Pin; @@ -83,6 +84,7 @@ pub(crate) struct Config { pub remote_reset_stream_max: usize, pub local_error_reset_streams_max: Option, pub settings: frame::Settings, + pub allowed_custom_settings: BTreeSet, } #[derive(Debug)] @@ -123,6 +125,7 @@ where .max_concurrent_streams() .map(|max| max as usize), local_max_error_reset_streams: config.local_error_reset_streams_max, + allowed_custom_settings: config.allowed_custom_settings.clone(), } } let streams = Streams::new(streams_config(&config)); diff --git a/src/proto/streams/mod.rs b/src/proto/streams/mod.rs index c4a83234..5b87f76e 100644 --- a/src/proto/streams/mod.rs +++ b/src/proto/streams/mod.rs @@ -29,6 +29,7 @@ use crate::frame::{StreamId, StreamIdOverflow}; use crate::proto::*; use bytes::Bytes; +use std::collections::BTreeSet; use std::time::Duration; #[derive(Debug)] @@ -72,4 +73,7 @@ pub struct Config { /// /// When this gets exceeded, we issue GOAWAYs. pub local_max_error_reset_streams: Option, + + /// Custom settings IDs to be tracked from the remote + pub allowed_custom_settings: BTreeSet, } diff --git a/src/proto/streams/send.rs b/src/proto/streams/send.rs index 8d729e8f..81948920 100644 --- a/src/proto/streams/send.rs +++ b/src/proto/streams/send.rs @@ -10,7 +10,7 @@ use bytes::Buf; use tokio::io::AsyncWrite; use std::cmp::Ordering; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::io; use std::task::{Context, Poll, Waker}; @@ -42,6 +42,7 @@ pub(super) struct Send { /// Custom settings custom_settings: BTreeMap, + allowed_custom_settings: BTreeSet, } /// A value to detect which public API has called `poll_reset`. @@ -62,6 +63,7 @@ impl Send { is_push_enabled: true, is_extended_connect_protocol_enabled: false, custom_settings: BTreeMap::new(), + allowed_custom_settings: config.allowed_custom_settings.clone(), } } @@ -439,7 +441,11 @@ impl Send { if let Some(val) = settings.is_extended_connect_protocol_enabled() { self.is_extended_connect_protocol_enabled = val; } - self.custom_settings.extend(settings.custom_settings()); + for id in &self.allowed_custom_settings { + if let Some(val) = settings.custom_setting(*id) { + self.custom_settings.insert(*id, val); + } + } // Applies an update to the remote endpoint's initial window size. // @@ -590,6 +596,6 @@ impl Send { } pub(crate) fn custom_setting(&self, id: u16) -> Option { - self.custom_settings.get(&id).map(|v| *v) + self.custom_settings.get(&id).copied() } } diff --git a/src/server.rs b/src/server.rs index e65e54df..938be415 100644 --- a/src/server.rs +++ b/src/server.rs @@ -122,6 +122,7 @@ use crate::{FlowControl, PingPong, RecvStream, SendStream}; use bytes::{Buf, Bytes}; use http::{HeaderMap, Method, Request, Response}; +use std::collections::BTreeSet; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; @@ -258,6 +259,9 @@ pub struct Builder { /// /// When this gets exceeded, we issue GOAWAYs. local_max_error_reset_streams: Option, + + /// Custom settings IDs to be tracked from the remote + allowed_custom_settings: BTreeSet, } /// Send a response back to the client @@ -675,8 +679,8 @@ impl Builder { settings: Settings::default(), initial_target_connection_window_size: None, max_send_buffer_size: proto::DEFAULT_MAX_SEND_BUFFER_SIZE, - local_max_error_reset_streams: Some(proto::DEFAULT_LOCAL_RESET_COUNT_MAX), + allowed_custom_settings: BTreeSet::new(), } } @@ -1058,6 +1062,16 @@ impl Builder { self } + /// By default, unknown settings recieved from the remote will be ignored. + /// + /// See [Section 6.5] in the HTTP/2 spec for more details. + /// + /// [Section 6.5]: https://httpwg.org/specs/rfc7540.html#rfc.section.6.5 + pub fn allow_custom_setting(&mut self, id: u16) -> &mut Self { + self.allowed_custom_settings.insert(id); + self + } + /// Creates a new configured HTTP/2 server backed by `io`. /// /// It is expected that `io` already be in an appropriate state to commence @@ -1420,6 +1434,7 @@ where .builder .local_max_error_reset_streams, settings: self.builder.settings.clone(), + allowed_custom_settings: self.builder.allowed_custom_settings.clone(), }, );