wt: patch web-transport-proto header interop for Cloudflare relay
This commit is contained in:
parent
aa4bddcba0
commit
523c601dc3
17 changed files with 2567 additions and 2 deletions
254
third_party/web-transport-proto/src/settings.rs
vendored
Normal file
254
third_party/web-transport-proto/src/settings.rs
vendored
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
|
||||
use thiserror::Error;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
|
||||
use super::{Frame, StreamUni, VarInt, VarIntUnexpectedEnd};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Setting(pub VarInt);
|
||||
|
||||
impl Setting {
|
||||
pub fn decode<B: Buf>(buf: &mut B) -> Result<Self, VarIntUnexpectedEnd> {
|
||||
Ok(Setting(VarInt::decode(buf)?))
|
||||
}
|
||||
|
||||
pub fn encode<B: BufMut>(&self, buf: &mut B) {
|
||||
self.0.encode(buf)
|
||||
}
|
||||
|
||||
// Reference : https://datatracker.ietf.org/doc/html/rfc9114#section-7.2.4.1
|
||||
pub fn is_grease(&self) -> bool {
|
||||
let val = self.0.into_inner();
|
||||
if val < 0x21 {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[allow(unknown_lints, clippy::manual_is_multiple_of)]
|
||||
{
|
||||
(val - 0x21) % 0x1f == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Setting {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
Setting::QPACK_MAX_TABLE_CAPACITY => write!(f, "QPACK_MAX_TABLE_CAPACITY"),
|
||||
Setting::MAX_FIELD_SECTION_SIZE => write!(f, "MAX_FIELD_SECTION_SIZE"),
|
||||
Setting::QPACK_BLOCKED_STREAMS => write!(f, "QPACK_BLOCKED_STREAMS"),
|
||||
Setting::ENABLE_CONNECT_PROTOCOL => write!(f, "ENABLE_CONNECT_PROTOCOL"),
|
||||
Setting::ENABLE_DATAGRAM => write!(f, "ENABLE_DATAGRAM"),
|
||||
Setting::ENABLE_DATAGRAM_DEPRECATED => write!(f, "ENABLE_DATAGRAM_DEPRECATED"),
|
||||
Setting::WEBTRANSPORT_ENABLE_DEPRECATED => write!(f, "WEBTRANSPORT_ENABLE_DEPRECATED"),
|
||||
Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED => {
|
||||
write!(f, "WEBTRANSPORT_MAX_SESSIONS_DEPRECATED")
|
||||
}
|
||||
Setting::WEBTRANSPORT_MAX_SESSIONS => write!(f, "WEBTRANSPORT_MAX_SESSIONS"),
|
||||
x if x.is_grease() => write!(f, "GREASE SETTING [{:x?}]", x.0.into_inner()),
|
||||
x => write!(f, "UNKNOWN_SETTING [{:x?}]", x.0.into_inner()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! settings {
|
||||
{$($name:ident = $val:expr,)*} => {
|
||||
impl Setting {
|
||||
$(pub const $name: Setting = Setting(VarInt::from_u32($val));)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settings! {
|
||||
// These are for HTTP/3 and we can ignore them
|
||||
QPACK_MAX_TABLE_CAPACITY = 0x1, // default is 0, which disables QPACK dynamic table
|
||||
MAX_FIELD_SECTION_SIZE = 0x6,
|
||||
QPACK_BLOCKED_STREAMS = 0x7,
|
||||
|
||||
// Both of these are required for WebTransport
|
||||
ENABLE_CONNECT_PROTOCOL = 0x8,
|
||||
ENABLE_DATAGRAM = 0x33,
|
||||
ENABLE_DATAGRAM_DEPRECATED = 0xFFD277, // still used by Chrome
|
||||
|
||||
// Removed in draft 06
|
||||
WEBTRANSPORT_ENABLE_DEPRECATED = 0x2b603742,
|
||||
WEBTRANSPORT_MAX_SESSIONS_DEPRECATED = 0x2b603743,
|
||||
|
||||
// New way to enable WebTransport
|
||||
WEBTRANSPORT_MAX_SESSIONS = 0xc671706a,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Clone)]
|
||||
pub enum SettingsError {
|
||||
#[error("unexpected end of input")]
|
||||
UnexpectedEnd,
|
||||
|
||||
#[error("unexpected stream type {0:?}")]
|
||||
UnexpectedStreamType(StreamUni),
|
||||
|
||||
#[error("unexpected frame {0:?}")]
|
||||
UnexpectedFrame(Frame),
|
||||
|
||||
#[error("invalid size")]
|
||||
InvalidSize,
|
||||
|
||||
#[error("io error: {0}")]
|
||||
Io(Arc<std::io::Error>),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for SettingsError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
SettingsError::Io(Arc::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
// A map of settings to values.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Settings(HashMap<Setting, VarInt>);
|
||||
|
||||
impl Settings {
|
||||
pub fn decode<B: Buf>(buf: &mut B) -> Result<Self, SettingsError> {
|
||||
let typ = StreamUni::decode(buf).map_err(|_| SettingsError::UnexpectedEnd)?;
|
||||
if typ != StreamUni::CONTROL {
|
||||
return Err(SettingsError::UnexpectedStreamType(typ));
|
||||
}
|
||||
|
||||
let (typ, mut data) = Frame::read(buf).map_err(|_| SettingsError::UnexpectedEnd)?;
|
||||
if typ != Frame::SETTINGS {
|
||||
return Err(SettingsError::UnexpectedFrame(typ));
|
||||
}
|
||||
|
||||
let mut settings = Settings::default();
|
||||
while data.has_remaining() {
|
||||
// These return a different error because retrying won't help.
|
||||
let id = Setting::decode(&mut data).map_err(|_| SettingsError::InvalidSize)?;
|
||||
let value = VarInt::decode(&mut data).map_err(|_| SettingsError::InvalidSize)?;
|
||||
// Only add if it is not grease
|
||||
if !id.is_grease() {
|
||||
settings.0.insert(id, value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
pub async fn read<S: AsyncRead + Unpin>(stream: &mut S) -> Result<Self, SettingsError> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
if stream.read_buf(&mut buf).await? == 0 {
|
||||
return Err(SettingsError::UnexpectedEnd);
|
||||
}
|
||||
|
||||
// Look at the buffer we've already read.
|
||||
let mut limit = std::io::Cursor::new(&buf);
|
||||
|
||||
match Settings::decode(&mut limit) {
|
||||
Ok(settings) => return Ok(settings),
|
||||
Err(SettingsError::UnexpectedEnd) => continue, // More data needed.
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode<B: BufMut>(&self, buf: &mut B) {
|
||||
StreamUni::CONTROL.encode(buf);
|
||||
Frame::SETTINGS.encode(buf);
|
||||
|
||||
// Encode to a temporary buffer so we can learn the length.
|
||||
// TODO avoid doing this, just use a fixed size varint.
|
||||
let mut tmp = Vec::new();
|
||||
for (id, value) in &self.0 {
|
||||
id.encode(&mut tmp);
|
||||
value.encode(&mut tmp);
|
||||
}
|
||||
|
||||
VarInt::from_u32(tmp.len() as u32).encode(buf);
|
||||
buf.put_slice(&tmp);
|
||||
}
|
||||
|
||||
pub async fn write<S: AsyncWrite + Unpin>(&self, stream: &mut S) -> Result<(), SettingsError> {
|
||||
// TODO avoid allocating to the heap
|
||||
let mut buf = BytesMut::new();
|
||||
self.encode(&mut buf);
|
||||
stream.write_all_buf(&mut buf).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enable_webtransport(&mut self, max_sessions: u32) {
|
||||
let max = VarInt::from_u32(max_sessions);
|
||||
|
||||
self.insert(Setting::ENABLE_CONNECT_PROTOCOL, VarInt::from_u32(1));
|
||||
self.insert(Setting::ENABLE_DATAGRAM, VarInt::from_u32(1));
|
||||
self.insert(Setting::ENABLE_DATAGRAM_DEPRECATED, VarInt::from_u32(1));
|
||||
self.insert(Setting::WEBTRANSPORT_MAX_SESSIONS, max);
|
||||
|
||||
// TODO remove when 07 is in the wild
|
||||
self.insert(Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED, max);
|
||||
self.insert(Setting::WEBTRANSPORT_ENABLE_DEPRECATED, VarInt::from_u32(1));
|
||||
}
|
||||
|
||||
// Returns the maximum number of sessions supported.
|
||||
pub fn supports_webtransport(&self) -> u64 {
|
||||
// Sent by Chrome 114.0.5735.198 (July 19, 2023)
|
||||
// Setting(1): 65536, // qpack_max_table_capacity
|
||||
// Setting(6): 16384, // max_field_section_size
|
||||
// Setting(7): 100, // qpack_blocked_streams
|
||||
// Setting(51): 1, // enable_datagram
|
||||
// Setting(16765559): 1 // enable_datagram_deprecated
|
||||
// Setting(727725890): 1, // webtransport_max_sessions_deprecated
|
||||
// Setting(4445614305): 454654587, // grease
|
||||
|
||||
// NOTE: The presence of ENABLE_WEBTRANSPORT implies ENABLE_CONNECT is supported.
|
||||
|
||||
let datagram = self
|
||||
.get(&Setting::ENABLE_DATAGRAM)
|
||||
.or(self.get(&Setting::ENABLE_DATAGRAM_DEPRECATED))
|
||||
.map(|v| v.into_inner());
|
||||
|
||||
if datagram != Some(1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The deprecated (before draft-07) way of enabling WebTransport was to send two parameters.
|
||||
// Both would send ENABLE=1 and the server would send MAX_SESSIONS=N to limit the sessions.
|
||||
// Now both just send MAX_SESSIONS, and a non-zero value means WebTransport is enabled.
|
||||
|
||||
if let Some(max) = self.get(&Setting::WEBTRANSPORT_MAX_SESSIONS) {
|
||||
return max.into_inner();
|
||||
}
|
||||
|
||||
let enabled = self
|
||||
.get(&Setting::WEBTRANSPORT_ENABLE_DEPRECATED)
|
||||
.map(|v| v.into_inner());
|
||||
if enabled != Some(1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Only the server is allowed to set this one, so if it's None we assume it's 1.
|
||||
self.get(&Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED)
|
||||
.map(|v| v.into_inner())
|
||||
.unwrap_or(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Settings {
|
||||
type Target = HashMap<Setting, VarInt>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Settings {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue