summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-http3/src/settings.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/neqo-http3/src/settings.rs')
-rw-r--r--third_party/rust/neqo-http3/src/settings.rs299
1 files changed, 299 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..9cd4b994b7
--- /dev/null
+++ b/third_party/rust/neqo-http3/src/settings.rs
@@ -0,0 +1,299 @@
+// 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 std::ops::Deref;
+
+use neqo_common::{Decoder, Encoder};
+use neqo_crypto::{ZeroRttCheckResult, ZeroRttChecker};
+
+use crate::{Error, Http3Parameters, Res};
+
+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
+ }
+ }
+}