diff --git a/src/client.rs b/src/client.rs index 0fbc4ca6..cb7e677a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -347,7 +347,7 @@ pub struct Builder { /// connection-level budget for DATA framing overhead. /// /// When this gets exhausted, we issue a GOAWAY with `ENHANCE_YOUR_CALM`. - data_frame_budget: usize, + data_frame_budget: proto::DataFrameBudget, } #[derive(Debug)] @@ -668,7 +668,7 @@ impl Builder { settings: Default::default(), stream_id: 1.into(), local_max_error_reset_streams: Some(proto::DEFAULT_LOCAL_RESET_COUNT_MAX), - data_frame_budget: proto::DEFAULT_DATA_FRAME_BUDGET, + data_frame_budget: proto::DataFrameBudget::Auto, } } @@ -1166,9 +1166,11 @@ impl Builder { /// When this budget is exhausted, the connection is closed with /// `ENHANCE_YOUR_CALM`. /// - /// The default is currently 25,600 bytes, but is subject to change. + /// By default, the budget is half the initial connection window, with a + /// minimum of 25,600 bytes. Increasing the connection window therefore + /// also increases the permitted framing overhead. pub fn data_frame_budget(&mut self, budget: usize) -> &mut Self { - self.data_frame_budget = budget; + self.data_frame_budget = proto::DataFrameBudget::Configured(budget); self } @@ -1362,7 +1364,9 @@ 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, - data_frame_budget: builder.data_frame_budget, + data_frame_budget: builder + .data_frame_budget + .resolve(builder.initial_target_connection_window_size), }, ); let send_request = SendRequest { diff --git a/src/proto/connection.rs b/src/proto/connection.rs index 26537a8d..757e2b27 100644 --- a/src/proto/connection.rs +++ b/src/proto/connection.rs @@ -86,6 +86,26 @@ pub(crate) struct Config { pub data_frame_budget: usize, } +#[derive(Clone, Copy, Debug)] +pub(crate) enum DataFrameBudget { + Auto, + Configured(usize), +} + +impl DataFrameBudget { + pub(crate) fn resolve(self, connection_window: Option) -> usize { + match self { + Self::Configured(budget) => budget, + Self::Auto => { + let window = connection_window.unwrap_or(DEFAULT_INITIAL_WINDOW_SIZE); + let budget = window as usize / 2; + + budget.max(DEFAULT_DATA_FRAME_BUDGET) + } + } + } +} + #[derive(Debug)] enum State { /// Currently open in a sane state @@ -641,3 +661,41 @@ where let _ = self.inner.streams.recv_eof(true); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn auto_data_frame_budget_scales_with_connection_window() { + assert_eq!( + DataFrameBudget::Auto.resolve(None), + DEFAULT_INITIAL_WINDOW_SIZE as usize / 2 + ); + assert_eq!( + DataFrameBudget::Auto.resolve(Some(DEFAULT_INITIAL_WINDOW_SIZE)), + DEFAULT_INITIAL_WINDOW_SIZE as usize / 2 + ); + assert_eq!(DataFrameBudget::Auto.resolve(Some(1024 * 1024)), 512 * 1024); + } + + #[test] + fn auto_data_frame_budget_has_minimum() { + assert_eq!( + DataFrameBudget::Auto.resolve(Some(1)), + DEFAULT_DATA_FRAME_BUDGET + ); + assert_eq!( + DataFrameBudget::Auto.resolve(Some(MAX_WINDOW_SIZE)), + MAX_WINDOW_SIZE as usize / 2 + ); + } + + #[test] + fn configured_data_frame_budget_is_unchanged() { + assert_eq!( + DataFrameBudget::Configured(123).resolve(Some(MAX_WINDOW_SIZE)), + 123 + ); + } +} diff --git a/src/proto/mod.rs b/src/proto/mod.rs index a9d3a9ac..5e3b4f40 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -6,7 +6,7 @@ mod ping_pong; mod settings; mod streams; -pub(crate) use self::connection::{Config, Connection}; +pub(crate) use self::connection::{Config, Connection, DataFrameBudget}; pub use self::error::{Error, Initiator}; pub(crate) use self::peer::{Dyn as DynPeer, Peer}; pub(crate) use self::ping_pong::UserPings; diff --git a/src/server.rs b/src/server.rs index d26dcf4b..316d6a18 100644 --- a/src/server.rs +++ b/src/server.rs @@ -262,7 +262,7 @@ pub struct Builder { /// connection-level budget for DATA framing overhead. /// /// When this gets exhausted, we issue a GOAWAY with `ENHANCE_YOUR_CALM`. - data_frame_budget: usize, + data_frame_budget: proto::DataFrameBudget, } /// Send a response back to the client @@ -661,7 +661,7 @@ impl Builder { 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), - data_frame_budget: proto::DEFAULT_DATA_FRAME_BUDGET, + data_frame_budget: proto::DataFrameBudget::Auto, } } @@ -1060,9 +1060,11 @@ impl Builder { /// When this budget is exhausted, the connection is closed with /// `ENHANCE_YOUR_CALM`. /// - /// The default is currently 25,600 bytes, but is subject to change. + /// By default, the budget is half the initial connection window, with a + /// minimum of 25,600 bytes. Increasing the connection window therefore + /// also increases the permitted framing overhead. pub fn data_frame_budget(&mut self, budget: usize) -> &mut Self { - self.data_frame_budget = budget; + self.data_frame_budget = proto::DataFrameBudget::Configured(budget); self } @@ -1531,7 +1533,10 @@ where .builder .local_max_error_reset_streams, settings: self.builder.settings.clone(), - data_frame_budget: self.builder.data_frame_budget, + data_frame_budget: self + .builder + .data_frame_budget + .resolve(self.builder.initial_target_connection_window_size), }, );