diff --git a/src/client.rs b/src/client.rs index ffeda607..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)] @@ -557,6 +561,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 @@ -663,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(), } } @@ -859,6 +874,26 @@ 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 + } + + /// 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 @@ -1335,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 484498a9..76fbe76b 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_setting(&self, id: u16) -> Option { + self.custom.get(&id).copied() + } + 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..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)); @@ -162,6 +165,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 +372,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/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 2a7abba0..81948920 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, BTreeSet}; use std::io; use std::task::{Context, Poll, Waker}; @@ -38,6 +39,10 @@ pub(super) struct Send { /// If extended connect protocol is enabled. is_extended_connect_protocol_enabled: bool, + + /// Custom settings + custom_settings: BTreeMap, + allowed_custom_settings: BTreeSet, } /// A value to detect which public API has called `poll_reset`. @@ -57,6 +62,8 @@ impl Send { prioritize: Prioritize::new(config), is_push_enabled: true, is_extended_connect_protocol_enabled: false, + custom_settings: BTreeMap::new(), + allowed_custom_settings: config.allowed_custom_settings.clone(), } } @@ -434,6 +441,11 @@ impl Send { if let Some(val) = settings.is_extended_connect_protocol_enabled() { self.is_extended_connect_protocol_enabled = val; } + 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. // @@ -582,4 +594,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).copied() + } } 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..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 @@ -497,6 +501,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 +606,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")] @@ -650,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(), } } @@ -1023,6 +1052,26 @@ 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 + } + + /// 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 @@ -1385,6 +1434,7 @@ where .builder .local_max_error_reset_streams, settings: self.builder.settings.clone(), + allowed_custom_settings: self.builder.allowed_custom_settings.clone(), }, );