use super::connect::DraftVersion;
use crate::settings::Setting;
use crate::varint::VarInt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Settings {
pub wt_enabled: VarInt,
pub wt_initial_max_streams_uni: VarInt,
pub wt_initial_max_streams_bidi: VarInt,
pub wt_initial_max_data: VarInt,
pub enable_webtransport_draft02: Option<bool>,
pub webtransport_max_sessions_draft07: Option<VarInt>,
pub wt_max_sessions_draft14: Option<VarInt>,
}
impl Default for Settings {
fn default() -> Self {
Self::new()
}
}
impl Settings {
pub const fn new() -> Self {
Self {
wt_enabled: VarInt::ZERO,
wt_initial_max_streams_uni: VarInt::ZERO,
wt_initial_max_streams_bidi: VarInt::ZERO,
wt_initial_max_data: VarInt::ZERO,
enable_webtransport_draft02: None,
webtransport_max_sessions_draft07: None,
wt_max_sessions_draft14: None,
}
}
pub fn wt_enabled(mut self, value: VarInt) -> Self {
self.wt_enabled = value;
self
}
pub fn wt_initial_max_streams_uni(mut self, max_streams: VarInt) -> Self {
self.wt_initial_max_streams_uni = max_streams;
self
}
pub fn wt_initial_max_streams_bidi(mut self, max_streams: VarInt) -> Self {
self.wt_initial_max_streams_bidi = max_streams;
self
}
pub fn wt_initial_max_data(mut self, max_data: VarInt) -> Self {
self.wt_initial_max_data = max_data;
self
}
pub fn enable_webtransport_draft02(mut self, enable: bool) -> Self {
self.enable_webtransport_draft02 = Some(enable);
self
}
pub fn webtransport_max_sessions_draft07(mut self, max_sessions: VarInt) -> Self {
self.webtransport_max_sessions_draft07 = Some(max_sessions);
self
}
pub fn wt_max_sessions_draft14(mut self, max_sessions: VarInt) -> Self {
self.wt_max_sessions_draft14 = Some(max_sessions);
self
}
pub(crate) fn from_payload(settings: &[Setting]) -> Option<Self> {
let mut wt = Self::new();
let mut found = false;
let mut seen_wt_ids = std::collections::HashSet::new();
for setting in settings {
match *setting {
Setting::WtEnabled(v) => {
debug_assert!(seen_wt_ids.insert(setting.id()));
wt.wt_enabled = v;
found = true;
}
Setting::WtInitialMaxStreamsUni(v) => {
debug_assert!(seen_wt_ids.insert(setting.id()));
wt.wt_initial_max_streams_uni = v;
found = true;
}
Setting::WtInitialMaxStreamsBidi(v) => {
debug_assert!(seen_wt_ids.insert(setting.id()));
wt.wt_initial_max_streams_bidi = v;
found = true;
}
Setting::WtInitialMaxData(v) => {
debug_assert!(seen_wt_ids.insert(setting.id()));
wt.wt_initial_max_data = v;
found = true;
}
Setting::EnableWebTransportDraft02(b) => {
debug_assert!(seen_wt_ids.insert(setting.id()));
wt.enable_webtransport_draft02 = Some(b);
found = true;
}
Setting::WebTransportMaxSessionsDraft07(v) => {
debug_assert!(seen_wt_ids.insert(setting.id()));
wt.webtransport_max_sessions_draft07 = Some(v);
found = true;
}
Setting::WtMaxSessionsDraft14(v) => {
debug_assert!(seen_wt_ids.insert(setting.id()));
wt.wt_max_sessions_draft14 = Some(v);
found = true;
}
Setting::QpackMaxTableCapacity(_)
| Setting::MaxFieldSectionSize(_)
| Setting::QpackBlockedStreams(_)
| Setting::EnableConnectProtocol(_)
| Setting::H3Datagram(_)
| Setting::Unknown(_) => {}
}
}
found.then_some(wt)
}
pub fn detect_draft_pattern(&self) -> Option<DraftVersion> {
if self.wt_enabled.get() > 0 {
return Some(DraftVersion::Draft15);
}
if self
.webtransport_max_sessions_draft07
.is_some_and(|v| v.get() > 0)
{
return Some(DraftVersion::Draft07);
}
if self.wt_max_sessions_draft14.is_some_and(|v| v.get() > 0) {
return Some(DraftVersion::Draft14);
}
if self.enable_webtransport_draft02 == Some(true) {
return Some(DraftVersion::Draft02);
}
None
}
pub fn is_enabled(&self) -> bool {
self.wt_enabled.get() > 0
|| self.enable_webtransport_draft02 == Some(true)
|| self
.webtransport_max_sessions_draft07
.is_some_and(|v| v.get() > 0)
|| self.wt_max_sessions_draft14.is_some_and(|v| v.get() > 0)
}
pub fn declares_flow_control(&self) -> bool {
self.wt_max_sessions_draft14.is_some_and(|v| v.get() > 1)
|| self.wt_initial_max_streams_uni.get() != 0
|| self.wt_initial_max_streams_bidi.get() != 0
|| self.wt_initial_max_data.get() != 0
}
pub fn flow_control_enabled_with_peer(&self, peer: &Self) -> bool {
self.declares_flow_control() && peer.declares_flow_control()
}
pub fn requires_initial_capsule_flow_control_compat(&self) -> bool {
match self.detect_draft_pattern() {
Some(DraftVersion::Draft14) => true,
Some(DraftVersion::Draft07) => {
self.wt_initial_max_streams_uni.get() != 0
|| self.wt_initial_max_streams_bidi.get() != 0
|| self.wt_initial_max_data.get() != 0
}
_ => false,
}
}
pub fn iter(&self) -> impl Iterator<Item = Setting> + '_ {
let entries = [
(self.wt_enabled != VarInt::ZERO).then_some(Setting::WtEnabled(self.wt_enabled)),
(self.wt_initial_max_streams_uni != VarInt::ZERO).then_some(
Setting::WtInitialMaxStreamsUni(self.wt_initial_max_streams_uni),
),
(self.wt_initial_max_streams_bidi != VarInt::ZERO).then_some(
Setting::WtInitialMaxStreamsBidi(self.wt_initial_max_streams_bidi),
),
(self.wt_initial_max_data != VarInt::ZERO)
.then_some(Setting::WtInitialMaxData(self.wt_initial_max_data)),
self.enable_webtransport_draft02
.map(Setting::EnableWebTransportDraft02),
self.webtransport_max_sessions_draft07
.map(Setting::WebTransportMaxSessionsDraft07),
self.wt_max_sessions_draft14
.map(Setting::WtMaxSessionsDraft14),
];
entries.into_iter().flatten()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn v(n: u64) -> VarInt {
VarInt::new(n).unwrap()
}
#[test]
fn test_settings_default() {
let settings = Settings::default();
assert_eq!(settings.wt_enabled, VarInt::ZERO);
assert!(!settings.is_enabled());
assert!(!settings.declares_flow_control());
}
#[test]
fn test_settings_builder() {
let settings = Settings::new()
.wt_enabled(v(1))
.wt_initial_max_streams_uni(v(100))
.wt_initial_max_streams_bidi(v(50))
.wt_initial_max_data(v(1024 * 1024));
assert_eq!(settings.wt_enabled, v(1));
assert_eq!(settings.wt_initial_max_streams_uni, v(100));
assert_eq!(settings.wt_initial_max_streams_bidi, v(50));
assert_eq!(settings.wt_initial_max_data, v(1024 * 1024));
assert!(settings.is_enabled());
assert!(settings.declares_flow_control());
}
#[test]
fn test_settings_draft02_07() {
let settings = Settings::new()
.enable_webtransport_draft02(true)
.webtransport_max_sessions_draft07(v(5));
assert!(settings.is_enabled());
assert_eq!(settings.enable_webtransport_draft02, Some(true));
assert_eq!(settings.webtransport_max_sessions_draft07, Some(v(5)));
}
#[test]
fn test_is_enabled_draft02() {
let settings = Settings::new().enable_webtransport_draft02(true);
assert!(settings.is_enabled());
let settings = Settings::new().enable_webtransport_draft02(false);
assert!(!settings.is_enabled());
}
#[test]
fn test_is_enabled_draft07() {
let settings = Settings::new().webtransport_max_sessions_draft07(v(1));
assert!(settings.is_enabled());
let settings = Settings::new().webtransport_max_sessions_draft07(v(0));
assert!(!settings.is_enabled());
}
#[test]
fn test_flow_control_enabled() {
let settings = Settings::new().wt_enabled(v(1));
assert!(!settings.declares_flow_control());
let settings = Settings::new().wt_enabled(v(2));
assert!(!settings.declares_flow_control());
let settings = Settings::new()
.wt_enabled(v(1))
.wt_initial_max_streams_uni(v(10));
assert!(settings.declares_flow_control());
let settings = Settings::new()
.wt_enabled(v(1))
.wt_initial_max_streams_bidi(v(5));
assert!(settings.declares_flow_control());
let settings = Settings::new()
.wt_enabled(v(1))
.wt_initial_max_data(v(1024));
assert!(settings.declares_flow_control());
}
#[test]
fn test_flow_control_enabled_with_peer() {
let local = Settings::new().wt_initial_max_streams_uni(v(10));
let peer = Settings::new().wt_enabled(v(1));
assert!(!local.flow_control_enabled_with_peer(&peer));
let peer = Settings::new().wt_initial_max_data(v(1));
assert!(local.flow_control_enabled_with_peer(&peer));
}
#[test]
fn test_settings_iter() {
let settings = Settings::new()
.wt_enabled(v(1))
.wt_initial_max_data(v(4096));
let entries: Vec<_> = settings.iter().collect();
assert_eq!(entries.len(), 2);
assert!(entries.contains(&Setting::WtEnabled(v(1))));
assert!(entries.contains(&Setting::WtInitialMaxData(v(4096))));
}
#[test]
fn test_settings_iter_with_draft02_07() {
let settings = Settings::new()
.wt_enabled(v(1))
.enable_webtransport_draft02(true)
.webtransport_max_sessions_draft07(v(3));
let entries: Vec<_> = settings.iter().collect();
assert_eq!(entries.len(), 3);
assert!(entries.contains(&Setting::WtEnabled(v(1))));
assert!(entries.contains(&Setting::EnableWebTransportDraft02(true)));
assert!(entries.contains(&Setting::WebTransportMaxSessionsDraft07(v(3))));
}
#[test]
fn test_from_payload_wt_setting() {
let unknown = Setting::from_wire(v(0xdead), v(1)).unwrap();
let entries = [
Setting::WtEnabled(v(1)),
Setting::WtInitialMaxStreamsUni(v(100)),
Setting::WtInitialMaxStreamsBidi(v(50)),
Setting::WtInitialMaxData(v(1024)),
Setting::EnableWebTransportDraft02(true),
Setting::WebTransportMaxSessionsDraft07(v(3)),
Setting::WtMaxSessionsDraft14(v(2)),
Setting::H3Datagram(true), unknown, ];
let wt = Settings::from_payload(&entries).unwrap();
assert_eq!(wt.wt_enabled, v(1));
assert_eq!(wt.wt_initial_max_streams_uni, v(100));
assert_eq!(wt.wt_initial_max_streams_bidi, v(50));
assert_eq!(wt.wt_initial_max_data, v(1024));
assert_eq!(wt.enable_webtransport_draft02, Some(true));
assert_eq!(wt.webtransport_max_sessions_draft07, Some(v(3)));
assert_eq!(wt.wt_max_sessions_draft14, Some(v(2)));
}
#[test]
fn test_from_payload_returns_none_without_wt_entries() {
let entries = [Setting::H3Datagram(true)];
assert!(Settings::from_payload(&entries).is_none());
}
#[test]
fn test_detect_draft_pattern_draft15() {
let settings = Settings::new().wt_enabled(v(1));
assert_eq!(settings.detect_draft_pattern(), Some(DraftVersion::Draft15));
}
#[test]
fn test_detect_draft_pattern_draft14() {
let settings = Settings::new().wt_max_sessions_draft14(v(1));
assert_eq!(settings.detect_draft_pattern(), Some(DraftVersion::Draft14));
}
#[test]
fn test_detect_draft_pattern_draft07() {
let settings = Settings::new().webtransport_max_sessions_draft07(v(1));
assert_eq!(settings.detect_draft_pattern(), Some(DraftVersion::Draft07));
}
#[test]
fn test_detect_draft_pattern_draft02() {
let settings = Settings::new().enable_webtransport_draft02(true);
assert_eq!(settings.detect_draft_pattern(), Some(DraftVersion::Draft02));
}
#[test]
fn test_detect_draft_pattern_none() {
let settings = Settings::new();
assert_eq!(settings.detect_draft_pattern(), None);
let settings = Settings::new().enable_webtransport_draft02(false);
assert_eq!(settings.detect_draft_pattern(), None);
let settings = Settings::new().wt_max_sessions_draft14(v(0));
assert_eq!(settings.detect_draft_pattern(), None);
}
#[test]
fn test_detect_draft_pattern_priority() {
let settings = Settings::new()
.webtransport_max_sessions_draft07(v(1))
.wt_max_sessions_draft14(v(1));
assert_eq!(settings.detect_draft_pattern(), Some(DraftVersion::Draft07));
let settings = Settings::new()
.wt_enabled(v(1))
.wt_max_sessions_draft14(v(1))
.webtransport_max_sessions_draft07(v(1));
assert_eq!(settings.detect_draft_pattern(), Some(DraftVersion::Draft15));
}
#[test]
fn test_detect_draft_pattern_safari_observed() {
let settings = Settings::new()
.webtransport_max_sessions_draft07(v(1))
.wt_initial_max_data(v(8_388_608))
.wt_initial_max_streams_uni(v(100))
.wt_initial_max_streams_bidi(v(100));
assert_eq!(settings.detect_draft_pattern(), Some(DraftVersion::Draft07));
}
#[test]
fn test_detect_draft_pattern_safari_legacy_combo() {
let settings = Settings::new()
.webtransport_max_sessions_draft07(v(1))
.wt_max_sessions_draft14(v(1))
.wt_initial_max_data(v(8_388_608))
.wt_initial_max_streams_uni(v(100))
.wt_initial_max_streams_bidi(v(100));
assert_eq!(settings.detect_draft_pattern(), Some(DraftVersion::Draft07));
}
#[test]
fn test_declares_flow_control_draft14_max_sessions() {
let settings = Settings::new().wt_max_sessions_draft14(v(2));
assert!(settings.declares_flow_control());
let settings = Settings::new().wt_max_sessions_draft14(v(1));
assert!(!settings.declares_flow_control());
}
#[test]
fn test_requires_initial_capsule_flow_control_compat_safari_observed() {
let settings = Settings::new()
.webtransport_max_sessions_draft07(v(1))
.wt_initial_max_streams_uni(v(100))
.wt_initial_max_streams_bidi(v(100))
.wt_initial_max_data(v(8 * 1024 * 1024));
assert!(settings.requires_initial_capsule_flow_control_compat());
}
#[test]
fn test_requires_initial_capsule_flow_control_compat_draft07_plain() {
let settings = Settings::new().webtransport_max_sessions_draft07(v(1));
assert!(!settings.requires_initial_capsule_flow_control_compat());
}
#[test]
fn test_is_enabled_draft14() {
let settings = Settings::new().wt_max_sessions_draft14(v(1));
assert!(settings.is_enabled());
let settings = Settings::new().wt_max_sessions_draft14(v(0));
assert!(!settings.is_enabled());
}
}