use super::error::{ErrorCode, H2Error};
use super::frame::Setting;
pub const DEFAULT_HEADER_TABLE_SIZE: u32 = 4096;
pub const DEFAULT_ENABLE_PUSH: bool = true;
pub const DEFAULT_MAX_CONCURRENT_STREAMS: u32 = 256;
pub const DEFAULT_INITIAL_WINDOW_SIZE: u32 = 65535;
pub const DEFAULT_MAX_FRAME_SIZE: u32 = 16384;
pub const DEFAULT_MAX_HEADER_LIST_SIZE: u32 = 65536;
pub const DEFAULT_CONTINUATION_TIMEOUT_MS: u64 = 5000;
pub const MAX_INITIAL_WINDOW_SIZE: u32 = 0x7fff_ffff;
pub const MAX_MAX_FRAME_SIZE: u32 = 0x00ff_ffff;
pub const MIN_MAX_FRAME_SIZE: u32 = 16384;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Settings {
pub header_table_size: u32,
pub enable_push: bool,
pub max_concurrent_streams: u32,
pub initial_window_size: u32,
pub max_frame_size: u32,
pub max_header_list_size: u32,
pub continuation_timeout_ms: u64,
}
impl Default for Settings {
fn default() -> Self {
Self {
header_table_size: DEFAULT_HEADER_TABLE_SIZE,
enable_push: DEFAULT_ENABLE_PUSH,
max_concurrent_streams: DEFAULT_MAX_CONCURRENT_STREAMS,
initial_window_size: DEFAULT_INITIAL_WINDOW_SIZE,
max_frame_size: DEFAULT_MAX_FRAME_SIZE,
max_header_list_size: DEFAULT_MAX_HEADER_LIST_SIZE,
continuation_timeout_ms: DEFAULT_CONTINUATION_TIMEOUT_MS,
}
}
}
impl Settings {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn client() -> Self {
Self {
enable_push: false,
..Self::default()
}
}
#[must_use]
pub fn server() -> Self {
Self::default()
}
pub fn apply(&mut self, setting: Setting) -> Result<(), H2Error> {
match setting {
Setting::HeaderTableSize(v) => {
self.header_table_size = v;
Ok(())
}
Setting::EnablePush(v) => {
self.enable_push = v;
Ok(())
}
Setting::MaxConcurrentStreams(v) => {
self.max_concurrent_streams = v;
Ok(())
}
Setting::InitialWindowSize(v) => {
if v > MAX_INITIAL_WINDOW_SIZE {
Err(H2Error::connection(
ErrorCode::FlowControlError,
"initial window size exceeds maximum (2^31-1)",
))
} else {
self.initial_window_size = v;
Ok(())
}
}
Setting::MaxFrameSize(v) => {
if (MIN_MAX_FRAME_SIZE..=MAX_MAX_FRAME_SIZE).contains(&v) {
self.max_frame_size = v;
Ok(())
} else {
Err(H2Error::protocol("max frame size out of valid range"))
}
}
Setting::MaxHeaderListSize(v) => {
self.max_header_list_size = v;
Ok(())
}
}
}
#[must_use]
pub fn to_settings(&self) -> Vec<Setting> {
self.to_settings_for_role(true)
}
#[must_use]
pub fn to_settings_for_role(&self, is_client: bool) -> Vec<Setting> {
let mut settings = Vec::with_capacity(if is_client { 6 } else { 5 });
settings.push(Setting::HeaderTableSize(self.header_table_size));
if is_client {
settings.push(Setting::EnablePush(self.enable_push));
}
settings.push(Setting::MaxConcurrentStreams(self.max_concurrent_streams));
settings.push(Setting::InitialWindowSize(self.initial_window_size));
settings.push(Setting::MaxFrameSize(self.max_frame_size));
settings.push(Setting::MaxHeaderListSize(self.max_header_list_size));
settings
}
#[must_use]
pub fn to_settings_minimal(&self) -> Vec<Setting> {
self.to_settings_minimal_for_role(true)
}
#[must_use]
pub fn to_settings_minimal_for_role(&self, is_client: bool) -> Vec<Setting> {
let mut settings = Vec::new();
if self.header_table_size != 4096 {
settings.push(Setting::HeaderTableSize(self.header_table_size));
}
if is_client && !self.enable_push {
settings.push(Setting::EnablePush(self.enable_push));
}
if self.max_concurrent_streams != u32::MAX {
settings.push(Setting::MaxConcurrentStreams(self.max_concurrent_streams));
}
if self.initial_window_size != 65535 {
settings.push(Setting::InitialWindowSize(self.initial_window_size));
}
if self.max_frame_size != 16384 {
settings.push(Setting::MaxFrameSize(self.max_frame_size));
}
if self.max_header_list_size != u32::MAX {
settings.push(Setting::MaxHeaderListSize(self.max_header_list_size));
}
settings
}
}
#[derive(Debug, Clone)]
pub struct SettingsBuilder {
settings: Settings,
}
impl SettingsBuilder {
#[must_use]
pub fn new() -> Self {
Self {
settings: Settings::default(),
}
}
#[must_use]
pub fn client() -> Self {
Self {
settings: Settings::client(),
}
}
#[must_use]
pub fn server() -> Self {
Self {
settings: Settings::server(),
}
}
#[must_use]
pub fn header_table_size(mut self, size: u32) -> Self {
self.settings.header_table_size = size;
self
}
#[must_use]
pub fn enable_push(mut self, enable: bool) -> Self {
self.settings.enable_push = enable;
self
}
#[must_use]
pub fn max_concurrent_streams(mut self, max: u32) -> Self {
self.settings.max_concurrent_streams = max;
self
}
#[must_use]
pub fn initial_window_size(mut self, size: u32) -> Self {
self.settings.initial_window_size = size.min(MAX_INITIAL_WINDOW_SIZE);
self
}
#[must_use]
pub fn max_frame_size(mut self, size: u32) -> Self {
self.settings.max_frame_size = size.clamp(MIN_MAX_FRAME_SIZE, MAX_MAX_FRAME_SIZE);
self
}
#[must_use]
pub fn max_header_list_size(mut self, size: u32) -> Self {
self.settings.max_header_list_size = size;
self
}
#[must_use]
pub fn continuation_timeout_ms(mut self, timeout_ms: u64) -> Self {
self.settings.continuation_timeout_ms = timeout_ms;
self
}
#[must_use]
pub fn build(self) -> Settings {
self.settings
}
}
impl Default for SettingsBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_settings() {
let settings = Settings::default();
assert_eq!(settings.header_table_size, 4096);
assert!(settings.enable_push);
assert_eq!(
settings.max_concurrent_streams,
DEFAULT_MAX_CONCURRENT_STREAMS
);
assert_eq!(settings.initial_window_size, 65535);
assert_eq!(settings.max_frame_size, 16384);
assert_eq!(settings.max_header_list_size, DEFAULT_MAX_HEADER_LIST_SIZE);
}
#[test]
fn test_client_settings() {
let settings = Settings::client();
assert!(!settings.enable_push);
}
#[test]
fn test_apply_valid_settings() {
let mut settings = Settings::default();
assert!(settings.apply(Setting::InitialWindowSize(32768)).is_ok());
assert_eq!(settings.initial_window_size, 32768);
}
#[test]
fn test_apply_invalid_initial_window_size() {
let mut settings = Settings::default();
let err = settings
.apply(Setting::InitialWindowSize(0x8000_0000))
.unwrap_err();
assert_eq!(err.code, ErrorCode::FlowControlError);
}
#[test]
fn test_apply_invalid_max_frame_size() {
let mut settings = Settings::default();
let err = settings.apply(Setting::MaxFrameSize(1000)).unwrap_err();
assert_eq!(err.code, ErrorCode::ProtocolError);
let err = settings
.apply(Setting::MaxFrameSize(0x0100_0000))
.unwrap_err();
assert_eq!(err.code, ErrorCode::ProtocolError);
}
#[test]
fn test_apply_max_frame_size_bounds() {
let mut settings = Settings::default();
assert!(
settings
.apply(Setting::MaxFrameSize(MIN_MAX_FRAME_SIZE))
.is_ok()
);
assert!(
settings
.apply(Setting::MaxFrameSize(MAX_MAX_FRAME_SIZE))
.is_ok()
);
}
#[test]
fn test_settings_builder() {
let settings = SettingsBuilder::new()
.header_table_size(8192)
.enable_push(false)
.max_concurrent_streams(100)
.initial_window_size(131_072)
.max_frame_size(32768)
.continuation_timeout_ms(2500)
.build();
assert_eq!(settings.header_table_size, 8192);
assert!(!settings.enable_push);
assert_eq!(settings.max_concurrent_streams, 100);
assert_eq!(settings.initial_window_size, 131_072);
assert_eq!(settings.max_frame_size, 32768);
assert_eq!(settings.continuation_timeout_ms, 2500);
}
#[test]
fn test_to_settings_minimal() {
let settings = SettingsBuilder::new()
.enable_push(false)
.max_concurrent_streams(100)
.build();
let minimal = settings.to_settings_minimal();
assert_eq!(minimal.len(), 3);
assert!(minimal.contains(&Setting::EnablePush(false)));
assert!(minimal.contains(&Setting::MaxConcurrentStreams(100)));
assert!(minimal.contains(&Setting::MaxHeaderListSize(DEFAULT_MAX_HEADER_LIST_SIZE)));
}
#[test]
fn test_to_settings_for_server_omits_enable_push() {
let settings = SettingsBuilder::server().enable_push(false).build();
let serialized = settings.to_settings_for_role(false);
assert!(
!serialized
.iter()
.any(|setting| matches!(setting, Setting::EnablePush(_)))
);
}
#[test]
fn test_to_settings_minimal_for_server_omits_enable_push() {
let settings = SettingsBuilder::server().enable_push(false).build();
let minimal = settings.to_settings_minimal_for_role(false);
assert!(
!minimal
.iter()
.any(|setting| matches!(setting, Setting::EnablePush(_)))
);
}
#[test]
fn settings_debug_clone_eq_default() {
let s = Settings::default();
let s2 = s.clone();
assert_eq!(s, s2);
let modified = Settings {
max_frame_size: 32768,
..Settings::default()
};
assert_ne!(s, modified);
let dbg = format!("{s:?}");
assert!(dbg.contains("Settings"));
}
#[test]
fn settings_builder_debug_clone() {
let b = SettingsBuilder::new();
let b2 = b.clone();
let dbg = format!("{b:?}");
assert!(dbg.contains("SettingsBuilder"));
assert_eq!(b.build(), b2.build());
}
}