use core::fmt;
use crate::qpack::Header;
use crate::varint::VarInt;
pub const PROTOCOL_WEBTRANSPORT_H3: &str = "webtransport-h3";
pub const PROTOCOL_WEBTRANSPORT_DRAFT02: &str = "webtransport";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DraftVersion {
Draft02,
Draft07,
Draft14,
Draft15,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ServerSettingsParams {
pub max_sessions: VarInt,
pub initial_max_streams_uni: VarInt,
pub initial_max_streams_bidi: VarInt,
pub initial_max_data: VarInt,
}
impl Default for ServerSettingsParams {
fn default() -> Self {
Self {
max_sessions: VarInt::from_static(1),
initial_max_streams_uni: VarInt::from_static(100),
initial_max_streams_bidi: VarInt::from_static(100),
initial_max_data: VarInt::from_static(8 * 1024 * 1024),
}
}
}
impl DraftVersion {
pub fn protocol_value(&self) -> &'static str {
match self {
Self::Draft02 | Self::Draft07 | Self::Draft14 => PROTOCOL_WEBTRANSPORT_DRAFT02,
Self::Draft15 => PROTOCOL_WEBTRANSPORT_H3,
}
}
pub fn build_server_settings(
&self,
params: &ServerSettingsParams,
) -> super::settings::Settings {
match self {
Self::Draft15 => super::settings::Settings::new()
.wt_enabled(VarInt::from_static(1))
.wt_initial_max_streams_uni(params.initial_max_streams_uni)
.wt_initial_max_streams_bidi(params.initial_max_streams_bidi)
.wt_initial_max_data(params.initial_max_data),
Self::Draft14 => super::settings::Settings::new()
.wt_max_sessions_draft14(params.max_sessions)
.webtransport_max_sessions_draft07(params.max_sessions),
Self::Draft07 => super::settings::Settings::new()
.webtransport_max_sessions_draft07(params.max_sessions),
Self::Draft02 => super::settings::Settings::new().enable_webtransport_draft02(true),
}
}
pub fn requires_initial_capsule_flow_control(&self) -> bool {
matches!(self, Self::Draft14)
}
pub fn requires_enable_connect_protocol(&self) -> bool {
matches!(self, Self::Draft07 | Self::Draft14 | Self::Draft15)
}
pub fn requires_reset_stream_at(&self) -> bool {
matches!(self, Self::Draft14 | Self::Draft15)
}
pub fn build_client_settings(
&self,
params: &ServerSettingsParams,
) -> super::settings::Settings {
match self {
Self::Draft15 => super::settings::Settings::new()
.wt_enabled(VarInt::from_static(1))
.wt_initial_max_streams_uni(params.initial_max_streams_uni)
.wt_initial_max_streams_bidi(params.initial_max_streams_bidi)
.wt_initial_max_data(params.initial_max_data),
Self::Draft14 => super::settings::Settings::new()
.wt_max_sessions_draft14(params.max_sessions)
.wt_initial_max_streams_uni(params.initial_max_streams_uni)
.wt_initial_max_streams_bidi(params.initial_max_streams_bidi)
.wt_initial_max_data(params.initial_max_data),
Self::Draft07 => super::settings::Settings::new()
.webtransport_max_sessions_draft07(params.max_sessions),
Self::Draft02 => super::settings::Settings::new().enable_webtransport_draft02(true),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectError {
InvalidScheme,
MissingAuthority,
MissingPath,
InvalidMethod,
InvalidProtocol,
InvalidEncoding,
}
impl fmt::Display for ConnectError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidScheme => write!(f, ":scheme must be \"https\""),
Self::MissingAuthority => write!(f, ":authority is required"),
Self::MissingPath => write!(f, ":path is required"),
Self::InvalidMethod => write!(f, ":method must be \"CONNECT\""),
Self::InvalidProtocol => {
write!(
f,
":protocol must be \"{}\" or \"{}\"",
PROTOCOL_WEBTRANSPORT_H3, PROTOCOL_WEBTRANSPORT_DRAFT02
)
}
Self::InvalidEncoding => write!(f, "header value is not valid UTF-8"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapabilityError {
MissingWebTransportSetting,
MissingEnableConnectProtocol,
MissingH3Datagram,
MissingQuicDatagram,
MissingResetStreamAt,
}
impl fmt::Display for CapabilityError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingWebTransportSetting => {
write!(f, "SETTINGS_WT_ENABLED with value > 0 is required")
}
Self::MissingEnableConnectProtocol => {
write!(
f,
"SETTINGS_ENABLE_CONNECT_PROTOCOL with value 1 is required"
)
}
Self::MissingH3Datagram => write!(f, "SETTINGS_H3_DATAGRAM with value 1 is required"),
Self::MissingQuicDatagram => {
write!(
f,
"max_datagram_frame_size transport parameter > 0 is required"
)
}
Self::MissingResetStreamAt => {
write!(f, "reset_stream_at transport parameter is required")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TransportCapabilities {
pub wt_enabled: bool,
pub enable_connect_protocol: bool,
pub h3_datagram: bool,
pub max_datagram_frame_size: bool,
pub reset_stream_at: bool,
}
impl TransportCapabilities {
pub const fn new() -> Self {
Self {
wt_enabled: false,
enable_connect_protocol: false,
h3_datagram: false,
max_datagram_frame_size: false,
reset_stream_at: false,
}
}
pub fn validate(&self) -> Result<(), CapabilityError> {
if !self.wt_enabled {
return Err(CapabilityError::MissingWebTransportSetting);
}
if !self.enable_connect_protocol {
return Err(CapabilityError::MissingEnableConnectProtocol);
}
if !self.h3_datagram {
return Err(CapabilityError::MissingH3Datagram);
}
if !self.max_datagram_frame_size {
return Err(CapabilityError::MissingQuicDatagram);
}
if !self.reset_stream_at {
return Err(CapabilityError::MissingResetStreamAt);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ConnectRequest {
pub draft_version: DraftVersion,
pub scheme: String,
pub authority: String,
pub path: String,
pub origin: Option<String>,
pub available_protocols: Vec<String>,
}
impl ConnectRequest {
pub fn new(
scheme: impl Into<String>,
authority: impl Into<String>,
path: impl Into<String>,
) -> Self {
Self {
draft_version: DraftVersion::Draft15,
scheme: scheme.into(),
authority: authority.into(),
path: path.into(),
origin: None,
available_protocols: Vec::new(),
}
}
pub fn draft_version(mut self, version: DraftVersion) -> Self {
self.draft_version = version;
self
}
pub fn origin(mut self, origin: impl Into<String>) -> Self {
self.origin = Some(origin.into());
self
}
pub fn available_protocols(mut self, protocols: Vec<String>) -> Self {
self.available_protocols = protocols;
self
}
pub fn from_headers(headers: &[(&[u8], &[u8])]) -> Result<Self, ConnectError> {
let mut method: Option<&[u8]> = None;
let mut protocol: Option<&[u8]> = None;
let mut scheme: Option<String> = None;
let mut authority: Option<String> = None;
let mut path: Option<String> = None;
let mut origin: Option<String> = None;
let mut wt_available_protocols: Option<String> = None;
for &(name, value) in headers {
match name {
b":method" => method = Some(value),
b":protocol" => protocol = Some(value),
b":scheme" => {
scheme = Some(
core::str::from_utf8(value)
.map_err(|_| ConnectError::InvalidEncoding)?
.to_string(),
);
}
b":authority" => {
authority = Some(
core::str::from_utf8(value)
.map_err(|_| ConnectError::InvalidEncoding)?
.to_string(),
);
}
b":path" => {
path = Some(
core::str::from_utf8(value)
.map_err(|_| ConnectError::InvalidEncoding)?
.to_string(),
);
}
b"origin" => {
origin = Some(
core::str::from_utf8(value)
.map_err(|_| ConnectError::InvalidEncoding)?
.to_string(),
);
}
b"wt-available-protocols" => {
wt_available_protocols = Some(
core::str::from_utf8(value)
.map_err(|_| ConnectError::InvalidEncoding)?
.to_string(),
);
}
_ => {}
}
}
match method {
Some(b"CONNECT") => {}
Some(_) => return Err(ConnectError::InvalidMethod),
None => return Err(ConnectError::InvalidMethod),
}
let draft_version = match protocol {
Some(p) => {
let p_str = core::str::from_utf8(p).map_err(|_| ConnectError::InvalidEncoding)?;
if p_str == PROTOCOL_WEBTRANSPORT_H3 {
DraftVersion::Draft15
} else if p_str == PROTOCOL_WEBTRANSPORT_DRAFT02 {
DraftVersion::Draft02
} else {
return Err(ConnectError::InvalidProtocol);
}
}
None => return Err(ConnectError::InvalidProtocol),
};
let mut req = Self {
draft_version,
scheme: scheme.unwrap_or_default(),
authority: authority.unwrap_or_default(),
path: path.unwrap_or_default(),
origin,
available_protocols: Vec::new(),
};
if let Some(ref wt_ap) = wt_available_protocols {
req.available_protocols = Self::parse_available_protocols(wt_ap);
}
Ok(req)
}
pub fn to_headers(&self) -> Result<Vec<Header>, crate::qpack::HeaderError> {
let mut headers = vec![
Header::new(b":method", b"CONNECT")?,
Header::new(b":protocol", self.draft_version.protocol_value().as_bytes())?,
Header::new(b":scheme", self.scheme.as_bytes())?,
Header::new(b":authority", self.authority.as_bytes())?,
Header::new(b":path", self.path.as_bytes())?,
];
if let Some(ref origin) = self.origin {
headers.push(Header::new(b"origin", origin.as_bytes())?);
}
if !self.available_protocols.is_empty() {
let value = self
.available_protocols
.iter()
.map(|p| format!("\"{}\"", p.replace('\\', "\\\\").replace('"', "\\\"")))
.collect::<Vec<_>>()
.join(", ");
headers.push(Header::new(b"wt-available-protocols", value.as_bytes())?);
}
Ok(headers)
}
pub fn validate(&self) -> Result<(), ConnectError> {
if self.scheme != "https" {
return Err(ConnectError::InvalidScheme);
}
if self.authority.is_empty() {
return Err(ConnectError::MissingAuthority);
}
if self.path.is_empty() {
return Err(ConnectError::MissingPath);
}
Ok(())
}
pub fn parse_available_protocols(header_value: &str) -> Vec<String> {
parse_sf_list_strings(header_value)
}
}
#[derive(Debug, Clone)]
pub struct ConnectResponse {
pub status: u16,
pub selected_protocol: Option<String>,
}
impl ConnectResponse {
pub fn new(status: u16) -> Self {
Self {
status,
selected_protocol: None,
}
}
pub fn with_protocol(mut self, protocol: impl Into<String>) -> Self {
self.selected_protocol = Some(protocol.into());
self
}
pub fn to_headers(&self) -> Result<Vec<Header>, crate::qpack::HeaderError> {
let mut headers = vec![Header::new(b":status", self.status.to_string().as_bytes())?];
if let Some(ref proto) = self.selected_protocol {
headers.push(Header::new(
b"wt-protocol",
format!("\"{}\"", proto.replace('\\', "\\\\").replace('"', "\\\"")).as_bytes(),
)?);
}
Ok(headers)
}
pub fn is_success(&self) -> bool {
self.status / 100 == 2
}
pub fn is_protocol_valid(&self, request: &ConnectRequest) -> bool {
match &self.selected_protocol {
None => {
request.available_protocols.is_empty()
}
Some(proto) => {
if request.available_protocols.is_empty() {
false
} else {
request.available_protocols.contains(proto)
}
}
}
}
pub fn parse_protocol(header_value: &str) -> Option<String> {
parse_sf_item_string(header_value)
}
}
fn parse_sf_list_strings(value: &str) -> Vec<String> {
let mut result = Vec::new();
for item in value.split(',') {
let item = item.trim();
if item.is_empty() {
continue;
}
match parse_sf_item_string(item) {
Some(s) => result.push(s),
None => {
return Vec::new();
}
}
}
result
}
fn parse_sf_item_string(value: &str) -> Option<String> {
let value = value.trim();
let value = strip_sf_parameters(value);
let value = value.trim();
if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
let inner = &value[1..value.len() - 1];
let unescaped = inner
.replace("\\\\", "\x00")
.replace("\\\"", "\"")
.replace('\x00', "\\");
Some(unescaped)
} else {
None
}
}
fn strip_sf_parameters(value: &str) -> &str {
let bytes = value.as_bytes();
let mut in_string = false;
let mut escaped = false;
for (i, &b) in bytes.iter().enumerate() {
if escaped {
escaped = false;
continue;
}
match b {
b'"' => in_string = !in_string,
b'\\' if in_string => escaped = true,
b';' if !in_string => return &value[..i],
_ => {}
}
}
value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_connect_request_validate_success() {
let req = ConnectRequest::new("https", "example.com", "/path");
assert!(req.validate().is_ok());
}
#[test]
fn test_connect_request_validate_invalid_scheme() {
let req = ConnectRequest::new("http", "example.com", "/");
assert_eq!(req.validate(), Err(ConnectError::InvalidScheme));
}
#[test]
fn test_connect_request_validate_missing_authority() {
let req = ConnectRequest::new("https", "", "/");
assert_eq!(req.validate(), Err(ConnectError::MissingAuthority));
}
#[test]
fn test_connect_request_validate_missing_path() {
let req = ConnectRequest::new("https", "example.com", "");
assert_eq!(req.validate(), Err(ConnectError::MissingPath));
}
#[test]
fn test_parse_available_protocols_basic() {
let result = ConnectRequest::parse_available_protocols("\"foo\", \"bar\"");
assert_eq!(result, vec!["foo".to_string(), "bar".to_string()]);
}
#[test]
fn test_parse_available_protocols_ignores_non_strings() {
let result = ConnectRequest::parse_available_protocols("\"foo\", token, 42");
assert!(result.is_empty());
}
#[test]
fn test_parse_available_protocols_with_parameters() {
let result = ConnectRequest::parse_available_protocols("\"foo\";q=1, \"bar\";q=0.5");
assert_eq!(result, vec!["foo".to_string(), "bar".to_string()]);
}
#[test]
fn test_parse_available_protocols_empty() {
let result = ConnectRequest::parse_available_protocols("");
assert!(result.is_empty());
}
#[test]
fn test_parse_protocol_basic() {
let result = ConnectResponse::parse_protocol("\"foo\"");
assert_eq!(result, Some("foo".to_string()));
}
#[test]
fn test_parse_protocol_non_string() {
let result = ConnectResponse::parse_protocol("token");
assert_eq!(result, None);
}
#[test]
fn test_parse_protocol_with_parameters() {
let result = ConnectResponse::parse_protocol("\"foo\";q=1");
assert_eq!(result, Some("foo".to_string()));
}
#[test]
fn test_parse_protocol_escape_quote() {
let result = ConnectResponse::parse_protocol("\"foo\\\"bar\"");
assert_eq!(result, Some("foo\"bar".to_string()));
}
#[test]
fn test_parse_protocol_escape_backslash() {
let result = ConnectResponse::parse_protocol("\"foo\\\\bar\"");
assert_eq!(result, Some("foo\\bar".to_string()));
}
#[test]
fn test_connect_response_is_success() {
assert!(ConnectResponse::new(200).is_success());
assert!(ConnectResponse::new(201).is_success());
assert!(!ConnectResponse::new(301).is_success());
assert!(!ConnectResponse::new(404).is_success());
assert!(!ConnectResponse::new(500).is_success());
}
#[test]
fn test_is_protocol_valid_no_available_protocols() {
let req = ConnectRequest::new("https", "example.com", "/");
let resp = ConnectResponse::new(200).with_protocol("foo");
assert!(!resp.is_protocol_valid(&req));
}
#[test]
fn test_is_protocol_valid_match() {
let req = ConnectRequest::new("https", "example.com", "/")
.available_protocols(vec!["foo".to_string(), "bar".to_string()]);
let resp = ConnectResponse::new(200).with_protocol("foo");
assert!(resp.is_protocol_valid(&req));
}
#[test]
fn test_is_protocol_valid_no_match() {
let req = ConnectRequest::new("https", "example.com", "/")
.available_protocols(vec!["foo".to_string()]);
let resp = ConnectResponse::new(200).with_protocol("baz");
assert!(!resp.is_protocol_valid(&req));
}
#[test]
fn test_is_protocol_valid_no_protocol_in_response_no_negotiation() {
let req = ConnectRequest::new("https", "example.com", "/");
let resp = ConnectResponse::new(200);
assert!(resp.is_protocol_valid(&req));
}
#[test]
fn test_is_protocol_valid_no_protocol_in_response_with_negotiation() {
let req = ConnectRequest::new("https", "example.com", "/")
.available_protocols(vec!["foo".to_string()]);
let resp = ConnectResponse::new(200);
assert!(!resp.is_protocol_valid(&req));
}
#[test]
fn test_connect_error_display() {
assert_eq!(
format!("{}", ConnectError::InvalidScheme),
":scheme must be \"https\""
);
assert_eq!(
format!("{}", ConnectError::MissingAuthority),
":authority is required"
);
assert_eq!(
format!("{}", ConnectError::MissingPath),
":path is required"
);
}
#[test]
fn test_connect_request_to_headers_basic() {
let req = ConnectRequest::new("https", "example.com", "/wt");
let headers = req.to_headers().unwrap();
assert_eq!(headers.len(), 5);
assert_eq!(headers[0].name(), b":method");
assert_eq!(headers[0].value(), b"CONNECT");
assert_eq!(headers[1].name(), b":protocol");
assert_eq!(headers[1].value(), PROTOCOL_WEBTRANSPORT_H3.as_bytes());
assert_eq!(headers[2].name(), b":scheme");
assert_eq!(headers[2].value(), b"https");
assert_eq!(headers[3].name(), b":authority");
assert_eq!(headers[3].value(), b"example.com");
assert_eq!(headers[4].name(), b":path");
assert_eq!(headers[4].value(), b"/wt");
}
#[test]
fn test_connect_request_to_headers_draft02() {
let req =
ConnectRequest::new("https", "example.com", "/wt").draft_version(DraftVersion::Draft02);
let headers = req.to_headers().unwrap();
assert_eq!(headers[1].name(), b":protocol");
assert_eq!(headers[1].value(), PROTOCOL_WEBTRANSPORT_DRAFT02.as_bytes());
}
#[test]
fn test_connect_request_to_headers_draft07() {
let req =
ConnectRequest::new("https", "example.com", "/wt").draft_version(DraftVersion::Draft07);
let headers = req.to_headers().unwrap();
assert_eq!(headers[1].name(), b":protocol");
assert_eq!(headers[1].value(), PROTOCOL_WEBTRANSPORT_DRAFT02.as_bytes());
}
#[test]
fn test_connect_request_to_headers_with_origin() {
let req =
ConnectRequest::new("https", "example.com", "/wt").origin("https://client.example");
let headers = req.to_headers().unwrap();
assert_eq!(headers.len(), 6);
assert_eq!(headers[5].name(), b"origin");
assert_eq!(headers[5].value(), b"https://client.example");
}
#[test]
fn test_connect_request_to_headers_with_available_protocols() {
let req = ConnectRequest::new("https", "example.com", "/wt")
.available_protocols(vec!["moq".to_string(), "chat".to_string()]);
let headers = req.to_headers().unwrap();
assert_eq!(headers.len(), 6);
assert_eq!(headers[5].name(), b"wt-available-protocols");
assert_eq!(headers[5].value(), b"\"moq\", \"chat\"");
}
#[test]
fn test_connect_request_from_headers_draft15() {
let headers: Vec<(&[u8], &[u8])> = vec![
(b":method", b"CONNECT"),
(b":protocol", b"webtransport-h3"),
(b":scheme", b"https"),
(b":authority", b"example.com"),
(b":path", b"/wt"),
];
let req = ConnectRequest::from_headers(&headers).unwrap();
assert_eq!(req.draft_version, DraftVersion::Draft15);
assert_eq!(req.scheme, "https");
assert_eq!(req.authority, "example.com");
assert_eq!(req.path, "/wt");
assert!(req.origin.is_none());
assert!(req.available_protocols.is_empty());
}
#[test]
fn test_connect_request_from_headers_draft02_compat() {
let headers: Vec<(&[u8], &[u8])> = vec![
(b":method", b"CONNECT"),
(b":protocol", b"webtransport"),
(b":scheme", b"https"),
(b":authority", b"example.com"),
(b":path", b"/"),
];
let req = ConnectRequest::from_headers(&headers).unwrap();
assert_eq!(req.draft_version, DraftVersion::Draft02);
assert_eq!(req.scheme, "https");
}
#[test]
fn test_connect_request_from_headers_invalid_method() {
let headers: Vec<(&[u8], &[u8])> =
vec![(b":method", b"GET"), (b":protocol", b"webtransport-h3")];
assert!(matches!(
ConnectRequest::from_headers(&headers),
Err(ConnectError::InvalidMethod)
));
}
#[test]
fn test_connect_request_from_headers_invalid_protocol() {
let headers: Vec<(&[u8], &[u8])> = vec![(b":method", b"CONNECT"), (b":protocol", b"h2c")];
assert!(matches!(
ConnectRequest::from_headers(&headers),
Err(ConnectError::InvalidProtocol)
));
}
#[test]
fn test_connect_request_from_headers_missing_method() {
let headers: Vec<(&[u8], &[u8])> = vec![(b":protocol", b"webtransport-h3")];
assert!(matches!(
ConnectRequest::from_headers(&headers),
Err(ConnectError::InvalidMethod)
));
}
#[test]
fn test_connect_request_from_headers_with_origin() {
let headers: Vec<(&[u8], &[u8])> = vec![
(b":method", b"CONNECT"),
(b":protocol", b"webtransport-h3"),
(b":scheme", b"https"),
(b":authority", b"example.com"),
(b":path", b"/wt"),
(b"origin", b"https://client.example"),
];
let req = ConnectRequest::from_headers(&headers).unwrap();
assert_eq!(req.origin, Some("https://client.example".to_string()));
}
#[test]
fn test_connect_request_roundtrip() {
let original =
ConnectRequest::new("https", "example.com", "/wt").origin("https://client.example");
let headers = original.to_headers().unwrap();
let pairs: Vec<(&[u8], &[u8])> = headers.iter().map(|h| (h.name(), h.value())).collect();
let parsed = ConnectRequest::from_headers(&pairs).unwrap();
assert_eq!(parsed.scheme, original.scheme);
assert_eq!(parsed.authority, original.authority);
assert_eq!(parsed.path, original.path);
assert_eq!(parsed.origin, original.origin);
}
#[test]
fn test_connect_response_to_headers_basic() {
let resp = ConnectResponse::new(200);
let headers = resp.to_headers().unwrap();
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name(), b":status");
assert_eq!(headers[0].value(), b"200");
}
#[test]
fn test_connect_response_to_headers_with_protocol() {
let resp = ConnectResponse::new(200).with_protocol("moq");
let headers = resp.to_headers().unwrap();
assert_eq!(headers.len(), 2);
assert_eq!(headers[1].name(), b"wt-protocol");
assert_eq!(headers[1].value(), b"\"moq\"");
}
#[test]
fn test_connect_error_display_new_variants() {
assert_eq!(
format!("{}", ConnectError::InvalidMethod),
":method must be \"CONNECT\""
);
assert!(format!("{}", ConnectError::InvalidProtocol).contains("webtransport-h3"));
assert_eq!(
format!("{}", ConnectError::InvalidEncoding),
"header value is not valid UTF-8"
);
}
#[test]
fn test_transport_capabilities_validate_success() {
let caps = TransportCapabilities {
wt_enabled: true,
enable_connect_protocol: true,
h3_datagram: true,
max_datagram_frame_size: true,
reset_stream_at: true,
};
assert!(caps.validate().is_ok());
}
#[test]
fn test_transport_capabilities_validate_missing_setting() {
let caps = TransportCapabilities::new();
assert_eq!(
caps.validate(),
Err(CapabilityError::MissingWebTransportSetting)
);
}
fn vi(value: u64) -> VarInt {
VarInt::new(value).unwrap()
}
#[test]
fn test_build_server_settings_draft15() {
let params = ServerSettingsParams {
initial_max_streams_uni: vi(500),
initial_max_streams_bidi: vi(300),
..Default::default()
};
let s = DraftVersion::Draft15.build_server_settings(¶ms);
assert_eq!(s.wt_enabled, vi(1));
assert_eq!(s.wt_initial_max_streams_uni, vi(500));
assert_eq!(s.wt_initial_max_streams_bidi, vi(300));
assert_eq!(
s.wt_initial_max_data,
ServerSettingsParams::default().initial_max_data
);
assert_eq!(s.webtransport_max_sessions_draft07, None);
assert_eq!(s.wt_max_sessions_draft14, None);
}
#[test]
fn test_build_server_settings_draft14() {
let params = ServerSettingsParams {
max_sessions: vi(100),
initial_max_streams_uni: vi(1000),
initial_max_streams_bidi: vi(1000),
initial_max_data: vi(8 * 1024 * 1024),
};
let s = DraftVersion::Draft14.build_server_settings(¶ms);
assert_eq!(s.wt_max_sessions_draft14, Some(vi(100)));
assert_eq!(s.webtransport_max_sessions_draft07, Some(vi(100)));
assert_eq!(s.wt_initial_max_streams_uni, VarInt::ZERO);
assert_eq!(s.wt_initial_max_streams_bidi, VarInt::ZERO);
assert_eq!(s.wt_initial_max_data, VarInt::ZERO);
}
#[test]
fn test_build_server_settings_draft07() {
let params = ServerSettingsParams::default();
let s = DraftVersion::Draft07.build_server_settings(¶ms);
assert_eq!(s.webtransport_max_sessions_draft07, Some(vi(1)));
assert_eq!(s.wt_enabled, VarInt::ZERO);
assert_eq!(s.wt_max_sessions_draft14, None);
assert_eq!(s.enable_webtransport_draft02, None);
}
#[test]
fn test_build_server_settings_draft02() {
let params = ServerSettingsParams::default();
let s = DraftVersion::Draft02.build_server_settings(¶ms);
assert_eq!(s.enable_webtransport_draft02, Some(true));
assert_eq!(s.wt_enabled, VarInt::ZERO);
assert_eq!(s.webtransport_max_sessions_draft07, None);
}
#[test]
fn test_build_server_settings_draft14_safari_roundtrip() {
let params = ServerSettingsParams::default();
let s = DraftVersion::Draft14.build_server_settings(¶ms);
assert_eq!(s.detect_draft_pattern(), Some(DraftVersion::Draft07));
assert_eq!(s.wt_max_sessions_draft14, Some(params.max_sessions));
assert_eq!(
s.webtransport_max_sessions_draft07,
Some(params.max_sessions)
);
}
#[test]
fn test_requires_initial_capsule_flow_control() {
assert!(!DraftVersion::Draft02.requires_initial_capsule_flow_control());
assert!(!DraftVersion::Draft07.requires_initial_capsule_flow_control());
assert!(DraftVersion::Draft14.requires_initial_capsule_flow_control());
assert!(!DraftVersion::Draft15.requires_initial_capsule_flow_control());
}
#[test]
fn test_requires_enable_connect_protocol() {
assert!(!DraftVersion::Draft02.requires_enable_connect_protocol());
assert!(DraftVersion::Draft07.requires_enable_connect_protocol());
assert!(DraftVersion::Draft14.requires_enable_connect_protocol());
assert!(DraftVersion::Draft15.requires_enable_connect_protocol());
}
#[test]
fn test_requires_reset_stream_at() {
assert!(!DraftVersion::Draft02.requires_reset_stream_at());
assert!(!DraftVersion::Draft07.requires_reset_stream_at());
assert!(DraftVersion::Draft14.requires_reset_stream_at());
assert!(DraftVersion::Draft15.requires_reset_stream_at());
}
#[test]
fn test_build_client_settings_draft15() {
let params = ServerSettingsParams::default();
let s = DraftVersion::Draft15.build_client_settings(¶ms);
assert_eq!(s.wt_enabled, vi(1));
assert_eq!(s.wt_initial_max_streams_uni, params.initial_max_streams_uni);
assert_eq!(
s.wt_initial_max_streams_bidi,
params.initial_max_streams_bidi
);
assert_eq!(s.wt_initial_max_data, params.initial_max_data);
assert_eq!(s.webtransport_max_sessions_draft07, None);
assert_eq!(s.wt_max_sessions_draft14, None);
assert_eq!(s.detect_draft_pattern(), Some(DraftVersion::Draft15));
}
#[test]
fn test_build_client_settings_draft14() {
let params = ServerSettingsParams::default();
let s = DraftVersion::Draft14.build_client_settings(¶ms);
assert_eq!(s.wt_max_sessions_draft14, Some(params.max_sessions));
assert_eq!(s.webtransport_max_sessions_draft07, None);
assert_eq!(s.wt_initial_max_streams_uni, params.initial_max_streams_uni);
assert_eq!(
s.wt_initial_max_streams_bidi,
params.initial_max_streams_bidi
);
assert_eq!(s.wt_initial_max_data, params.initial_max_data);
assert_eq!(s.wt_enabled, VarInt::ZERO);
assert_eq!(s.detect_draft_pattern(), Some(DraftVersion::Draft14));
}
#[test]
fn test_build_client_settings_draft07() {
let params = ServerSettingsParams::default();
let s = DraftVersion::Draft07.build_client_settings(¶ms);
assert_eq!(
s.webtransport_max_sessions_draft07,
Some(params.max_sessions)
);
assert_eq!(s.wt_max_sessions_draft14, None);
assert_eq!(s.wt_enabled, VarInt::ZERO);
assert_eq!(s.enable_webtransport_draft02, None);
assert_eq!(s.detect_draft_pattern(), Some(DraftVersion::Draft07));
}
#[test]
fn test_build_client_settings_draft02() {
let params = ServerSettingsParams::default();
let s = DraftVersion::Draft02.build_client_settings(¶ms);
assert_eq!(s.enable_webtransport_draft02, Some(true));
assert_eq!(s.wt_enabled, VarInt::ZERO);
assert_eq!(s.webtransport_max_sessions_draft07, None);
assert_eq!(s.wt_max_sessions_draft14, None);
assert_eq!(s.detect_draft_pattern(), Some(DraftVersion::Draft02));
}
}