diff options
Diffstat (limited to 'third_party/rust/neqo-http3/src/settings.rs')
-rw-r--r-- | third_party/rust/neqo-http3/src/settings.rs | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/third_party/rust/neqo-http3/src/settings.rs b/third_party/rust/neqo-http3/src/settings.rs new file mode 100644 index 0000000000..1e952dae6d --- /dev/null +++ b/third_party/rust/neqo-http3/src/settings.rs @@ -0,0 +1,296 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::module_name_repetitions)] + +use crate::{Error, Http3Parameters, Res}; +use neqo_common::{Decoder, Encoder}; +use neqo_crypto::{ZeroRttCheckResult, ZeroRttChecker}; +use std::ops::Deref; + +type SettingsType = u64; + +/// Increment this version number if a new setting is added and that might +/// cause 0-RTT to be accepted where shouldn't be. +const SETTINGS_ZERO_RTT_VERSION: u64 = 1; + +const SETTINGS_MAX_HEADER_LIST_SIZE: SettingsType = 0x6; +const SETTINGS_QPACK_MAX_TABLE_CAPACITY: SettingsType = 0x1; +const SETTINGS_QPACK_BLOCKED_STREAMS: SettingsType = 0x7; +const SETTINGS_ENABLE_WEB_TRANSPORT: SettingsType = 0x2b60_3742; +// draft-ietf-masque-h3-datagram-04. +// We also use this old value because the current web-platform test only supports +// this value. +const SETTINGS_H3_DATAGRAM_DRAFT04: SettingsType = 0x00ff_d277; + +const SETTINGS_H3_DATAGRAM: SettingsType = 0x33; + +pub const H3_RESERVED_SETTINGS: &[SettingsType] = &[0x2, 0x3, 0x4, 0x5]; + +#[derive(Clone, PartialEq, Eq, Debug, Copy)] +pub enum HSettingType { + MaxHeaderListSize, + MaxTableCapacity, + BlockedStreams, + EnableWebTransport, + EnableH3Datagram, +} + +fn hsetting_default(setting_type: HSettingType) -> u64 { + match setting_type { + HSettingType::MaxHeaderListSize => 1 << 62, + HSettingType::MaxTableCapacity + | HSettingType::BlockedStreams + | HSettingType::EnableWebTransport + | HSettingType::EnableH3Datagram => 0, + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HSetting { + pub setting_type: HSettingType, + pub value: u64, +} + +impl HSetting { + #[must_use] + pub fn new(setting_type: HSettingType, value: u64) -> Self { + Self { + setting_type, + value, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct HSettings { + settings: Vec<HSetting>, +} + +impl HSettings { + #[must_use] + pub fn new(settings: &[HSetting]) -> Self { + Self { + settings: settings.to_vec(), + } + } + + #[must_use] + pub fn get(&self, setting: HSettingType) -> u64 { + match self.settings.iter().find(|s| s.setting_type == setting) { + Some(v) => v.value, + None => hsetting_default(setting), + } + } + + pub fn encode_frame_contents(&self, enc: &mut Encoder) { + enc.encode_vvec_with(|enc_inner| { + for iter in &self.settings { + match iter.setting_type { + HSettingType::MaxHeaderListSize => { + enc_inner.encode_varint(SETTINGS_MAX_HEADER_LIST_SIZE); + enc_inner.encode_varint(iter.value); + } + HSettingType::MaxTableCapacity => { + enc_inner.encode_varint(SETTINGS_QPACK_MAX_TABLE_CAPACITY); + enc_inner.encode_varint(iter.value); + } + HSettingType::BlockedStreams => { + enc_inner.encode_varint(SETTINGS_QPACK_BLOCKED_STREAMS); + enc_inner.encode_varint(iter.value); + } + HSettingType::EnableWebTransport => { + enc_inner.encode_varint(SETTINGS_ENABLE_WEB_TRANSPORT); + enc_inner.encode_varint(iter.value); + } + HSettingType::EnableH3Datagram => { + if iter.value == 1 { + enc_inner.encode_varint(SETTINGS_H3_DATAGRAM_DRAFT04); + enc_inner.encode_varint(iter.value); + enc_inner.encode_varint(SETTINGS_H3_DATAGRAM); + enc_inner.encode_varint(iter.value); + } + } + } + } + }); + } + + /// # Errors + /// Returns an error if settings types are reserved of settings value are not permitted. + pub fn decode_frame_contents(&mut self, dec: &mut Decoder) -> Res<()> { + while dec.remaining() > 0 { + let t = dec.decode_varint(); + let v = dec.decode_varint(); + + if let Some(settings_type) = t { + if H3_RESERVED_SETTINGS.contains(&settings_type) { + return Err(Error::HttpSettings); + } + } + match (t, v) { + (Some(SETTINGS_MAX_HEADER_LIST_SIZE), Some(value)) => self + .settings + .push(HSetting::new(HSettingType::MaxHeaderListSize, value)), + (Some(SETTINGS_QPACK_MAX_TABLE_CAPACITY), Some(value)) => self + .settings + .push(HSetting::new(HSettingType::MaxTableCapacity, value)), + (Some(SETTINGS_QPACK_BLOCKED_STREAMS), Some(value)) => self + .settings + .push(HSetting::new(HSettingType::BlockedStreams, value)), + (Some(SETTINGS_ENABLE_WEB_TRANSPORT), Some(value)) => { + if value > 1 { + return Err(Error::HttpSettings); + } + self.settings + .push(HSetting::new(HSettingType::EnableWebTransport, value)); + } + (Some(SETTINGS_H3_DATAGRAM_DRAFT04), Some(value)) => { + if value > 1 { + return Err(Error::HttpSettings); + } + if !self + .settings + .iter() + .any(|s| s.setting_type == HSettingType::EnableH3Datagram) + { + self.settings + .push(HSetting::new(HSettingType::EnableH3Datagram, value)); + } + } + (Some(SETTINGS_H3_DATAGRAM), Some(value)) => { + if value > 1 { + return Err(Error::HttpSettings); + } + if !self + .settings + .iter() + .any(|s| s.setting_type == HSettingType::EnableH3Datagram) + { + self.settings + .push(HSetting::new(HSettingType::EnableH3Datagram, value)); + } + } + // other supported settings here + (Some(_), Some(_)) => {} // ignore unknown setting, it is fine. + _ => return Err(Error::NotEnoughData), + }; + } + Ok(()) + } +} + +impl Deref for HSettings { + type Target = [HSetting]; + fn deref(&self) -> &Self::Target { + &self.settings + } +} + +impl From<&Http3Parameters> for HSettings { + fn from(conn_param: &Http3Parameters) -> Self { + Self { + settings: vec![ + HSetting { + setting_type: HSettingType::MaxTableCapacity, + value: conn_param.get_max_table_size_decoder(), + }, + HSetting { + setting_type: HSettingType::BlockedStreams, + value: u64::from(conn_param.get_max_blocked_streams()), + }, + HSetting { + setting_type: HSettingType::EnableWebTransport, + value: u64::from(conn_param.get_webtransport()), + }, + HSetting { + setting_type: HSettingType::EnableH3Datagram, + value: u64::from(conn_param.get_http3_datagram()), + }, + ], + } + } +} + +#[derive(Debug)] +pub struct HttpZeroRttChecker { + settings: Http3Parameters, +} + +impl HttpZeroRttChecker { + /// Right now we only have QPACK settings, so that is all this takes. + #[must_use] + pub fn new(settings: Http3Parameters) -> Self { + Self { settings } + } + + /// Save the settings that matter for 0-RTT. + #[must_use] + pub fn save(settings: &Http3Parameters) -> Vec<u8> { + let mut enc = Encoder::new(); + enc.encode_varint(SETTINGS_ZERO_RTT_VERSION) + .encode_varint(SETTINGS_QPACK_MAX_TABLE_CAPACITY) + .encode_varint(settings.get_max_table_size_decoder()) + .encode_varint(SETTINGS_QPACK_BLOCKED_STREAMS) + .encode_varint(settings.get_max_blocked_streams()); + if settings.get_webtransport() { + enc.encode_varint(SETTINGS_ENABLE_WEB_TRANSPORT) + .encode_varint(true); + } + if settings.get_http3_datagram() { + enc.encode_varint(SETTINGS_H3_DATAGRAM).encode_varint(true); + } + enc.into() + } +} + +impl ZeroRttChecker for HttpZeroRttChecker { + fn check(&self, token: &[u8]) -> ZeroRttCheckResult { + let mut dec = Decoder::from(token); + + // Read and check the version. + if let Some(version) = dec.decode_varint() { + if version != SETTINGS_ZERO_RTT_VERSION { + return ZeroRttCheckResult::Reject; + } + } else { + return ZeroRttCheckResult::Fail; + } + + // Now treat the rest as a settings frame. + let mut settings = HSettings::new(&[]); + if settings.decode_frame_contents(&mut dec).is_err() { + return ZeroRttCheckResult::Fail; + } + if settings.iter().all(|setting| match setting.setting_type { + HSettingType::BlockedStreams => { + u64::from(self.settings.get_max_blocked_streams()) >= setting.value + } + HSettingType::MaxTableCapacity => { + self.settings.get_max_table_size_decoder() >= setting.value + } + HSettingType::EnableWebTransport => { + if setting.value > 1 { + return false; + } + let value = setting.value == 1; + self.settings.get_webtransport() || !value + } + HSettingType::EnableH3Datagram => { + if setting.value > 1 { + return false; + } + let value = setting.value == 1; + self.settings.get_http3_datagram() || !value + } + HSettingType::MaxHeaderListSize => true, + }) { + ZeroRttCheckResult::Accept + } else { + ZeroRttCheckResult::Reject + } + } +} |