aboutsummaryrefslogtreecommitdiff
path: root/src/h3/frame.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/h3/frame.rs')
-rw-r--r--src/h3/frame.rs406
1 files changed, 364 insertions, 42 deletions
diff --git a/src/h3/frame.rs b/src/h3/frame.rs
index 085524b..46b802d 100644
--- a/src/h3/frame.rs
+++ b/src/h3/frame.rs
@@ -26,7 +26,8 @@
use super::Result;
-use crate::octets;
+#[cfg(feature = "qlog")]
+use qlog::events::h3::Http3Frame;
pub const DATA_FRAME_TYPE_ID: u64 = 0x0;
pub const HEADERS_FRAME_TYPE_ID: u64 = 0x1;
@@ -35,11 +36,13 @@ pub const SETTINGS_FRAME_TYPE_ID: u64 = 0x4;
pub const PUSH_PROMISE_FRAME_TYPE_ID: u64 = 0x5;
pub const GOAWAY_FRAME_TYPE_ID: u64 = 0x6;
pub const MAX_PUSH_FRAME_TYPE_ID: u64 = 0xD;
+pub const PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID: u64 = 0xF0700;
+pub const PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID: u64 = 0xF0701;
-const SETTINGS_QPACK_MAX_TABLE_CAPACITY: u64 = 0x1;
-const SETTINGS_MAX_HEADER_LIST_SIZE: u64 = 0x6;
-const SETTINGS_QPACK_BLOCKED_STREAMS: u64 = 0x7;
-const SETTINGS_H3_DATAGRAM: u64 = 0x276;
+pub const SETTINGS_QPACK_MAX_TABLE_CAPACITY: u64 = 0x1;
+pub const SETTINGS_MAX_FIELD_SECTION_SIZE: u64 = 0x6;
+pub const SETTINGS_QPACK_BLOCKED_STREAMS: u64 = 0x7;
+pub const SETTINGS_H3_DATAGRAM: u64 = 0x276;
// Permit between 16 maximally-encoded and 128 minimally-encoded SETTINGS.
const MAX_SETTINGS_PAYLOAD_SIZE: usize = 256;
@@ -59,11 +62,12 @@ pub enum Frame {
},
Settings {
- max_header_list_size: Option<u64>,
+ max_field_section_size: Option<u64>,
qpack_max_table_capacity: Option<u64>,
qpack_blocked_streams: Option<u64>,
h3_datagram: Option<u64>,
grease: Option<(u64, u64)>,
+ raw: Option<Vec<(u64, u64)>>,
},
PushPromise {
@@ -79,7 +83,20 @@ pub enum Frame {
push_id: u64,
},
- Unknown,
+ PriorityUpdateRequest {
+ prioritized_element_id: u64,
+ priority_field_value: Vec<u8>,
+ },
+
+ PriorityUpdatePush {
+ prioritized_element_id: u64,
+ priority_field_value: Vec<u8>,
+ },
+
+ Unknown {
+ raw_type: u64,
+ payload_length: u64,
+ },
}
impl Frame {
@@ -116,7 +133,14 @@ impl Frame {
push_id: b.get_varint()?,
},
- _ => Frame::Unknown,
+ PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID |
+ PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID =>
+ parse_priority_update(frame_type, payload_length, &mut b)?,
+
+ _ => Frame::Unknown {
+ raw_type: frame_type,
+ payload_length,
+ },
};
Ok(frame)
@@ -148,16 +172,17 @@ impl Frame {
},
Frame::Settings {
- max_header_list_size,
+ max_field_section_size,
qpack_max_table_capacity,
qpack_blocked_streams,
h3_datagram,
grease,
+ ..
} => {
let mut len = 0;
- if let Some(val) = max_header_list_size {
- len += octets::varint_len(SETTINGS_MAX_HEADER_LIST_SIZE);
+ if let Some(val) = max_field_section_size {
+ len += octets::varint_len(SETTINGS_MAX_FIELD_SECTION_SIZE);
len += octets::varint_len(*val);
}
@@ -184,8 +209,8 @@ impl Frame {
b.put_varint(SETTINGS_FRAME_TYPE_ID)?;
b.put_varint(len as u64)?;
- if let Some(val) = max_header_list_size {
- b.put_varint(SETTINGS_MAX_HEADER_LIST_SIZE)?;
+ if let Some(val) = max_field_section_size {
+ b.put_varint(SETTINGS_MAX_FIELD_SECTION_SIZE)?;
b.put_varint(*val as u64)?;
}
@@ -236,22 +261,161 @@ impl Frame {
b.put_varint(*push_id)?;
},
- Frame::Unknown => unreachable!(),
+ Frame::PriorityUpdateRequest {
+ prioritized_element_id,
+ priority_field_value,
+ } => {
+ let len = octets::varint_len(*prioritized_element_id) +
+ priority_field_value.len();
+
+ b.put_varint(PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID)?;
+ b.put_varint(len as u64)?;
+
+ b.put_varint(*prioritized_element_id as u64)?;
+ b.put_bytes(priority_field_value)?;
+ },
+
+ Frame::PriorityUpdatePush {
+ prioritized_element_id,
+ priority_field_value,
+ } => {
+ let len = octets::varint_len(*prioritized_element_id) +
+ priority_field_value.len();
+
+ b.put_varint(PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID)?;
+ b.put_varint(len as u64)?;
+
+ b.put_varint(*prioritized_element_id as u64)?;
+ b.put_bytes(priority_field_value)?;
+ },
+
+ Frame::Unknown { .. } => unreachable!(),
}
Ok(before - b.cap())
}
+
+ #[cfg(feature = "qlog")]
+ pub fn to_qlog(&self) -> Http3Frame {
+ match self {
+ Frame::Data { .. } => Http3Frame::Data { raw: None },
+
+ // Qlog expects the `headers` to be represented as an array of
+ // name:value pairs. At this stage, we only have the qpack block, so
+ // populate the field with an empty vec.
+ Frame::Headers { .. } => Http3Frame::Headers { headers: vec![] },
+
+ Frame::CancelPush { push_id } =>
+ Http3Frame::CancelPush { push_id: *push_id },
+
+ Frame::Settings {
+ max_field_section_size,
+ qpack_max_table_capacity,
+ qpack_blocked_streams,
+ h3_datagram,
+ grease,
+ ..
+ } => {
+ let mut settings = vec![];
+
+ if let Some(v) = max_field_section_size {
+ settings.push(qlog::events::h3::Setting {
+ name: "MAX_FIELD_SECTION_SIZE".to_string(),
+ value: *v,
+ });
+ }
+
+ if let Some(v) = qpack_max_table_capacity {
+ settings.push(qlog::events::h3::Setting {
+ name: "QPACK_MAX_TABLE_CAPACITY".to_string(),
+ value: *v,
+ });
+ }
+
+ if let Some(v) = qpack_blocked_streams {
+ settings.push(qlog::events::h3::Setting {
+ name: "QPACK_BLOCKED_STREAMS".to_string(),
+ value: *v,
+ });
+ }
+
+ if let Some(v) = h3_datagram {
+ settings.push(qlog::events::h3::Setting {
+ name: "H3_DATAGRAM".to_string(),
+ value: *v,
+ });
+ }
+
+ if let Some((k, v)) = grease {
+ settings.push(qlog::events::h3::Setting {
+ name: k.to_string(),
+ value: *v,
+ });
+ }
+
+ qlog::events::h3::Http3Frame::Settings { settings }
+ },
+
+ // Qlog expects the `headers` to be represented as an array of
+ // name:value pairs. At this stage, we only have the qpack block, so
+ // populate the field with an empty vec.
+ Frame::PushPromise { push_id, .. } => Http3Frame::PushPromise {
+ push_id: *push_id,
+ headers: vec![],
+ },
+
+ Frame::GoAway { id } => Http3Frame::Goaway { id: *id },
+
+ Frame::MaxPushId { push_id } =>
+ Http3Frame::MaxPushId { push_id: *push_id },
+
+ Frame::PriorityUpdateRequest {
+ prioritized_element_id,
+ priority_field_value,
+ } => Http3Frame::PriorityUpdate {
+ target_stream_type:
+ qlog::events::h3::H3PriorityTargetStreamType::Request,
+ prioritized_element_id: *prioritized_element_id,
+ priority_field_value: String::from_utf8_lossy(
+ priority_field_value,
+ )
+ .into_owned(),
+ },
+
+ Frame::PriorityUpdatePush {
+ prioritized_element_id,
+ priority_field_value,
+ } => Http3Frame::PriorityUpdate {
+ target_stream_type:
+ qlog::events::h3::H3PriorityTargetStreamType::Request,
+ prioritized_element_id: *prioritized_element_id,
+ priority_field_value: String::from_utf8_lossy(
+ priority_field_value,
+ )
+ .into_owned(),
+ },
+
+ Frame::Unknown {
+ raw_type,
+ payload_length,
+ } => Http3Frame::Unknown {
+ raw_frame_type: *raw_type,
+ raw_length: Some(*payload_length as u32),
+ raw: None,
+ },
+ }
+ }
}
impl std::fmt::Debug for Frame {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
- Frame::Data { payload } => {
- write!(f, "DATA len={}", payload.len())?;
+ Frame::Data { .. } => {
+ write!(f, "DATA")?;
},
- Frame::Headers { header_block } => {
- write!(f, "HEADERS len={}", header_block.len())?;
+ Frame::Headers { .. } => {
+ write!(f, "HEADERS")?;
},
Frame::CancelPush { push_id } => {
@@ -259,12 +423,13 @@ impl std::fmt::Debug for Frame {
},
Frame::Settings {
- max_header_list_size,
+ max_field_section_size,
qpack_max_table_capacity,
qpack_blocked_streams,
+ raw,
..
} => {
- write!(f, "SETTINGS max_headers={:?}, qpack_max_table={:?}, qpack_blocked={:?} ", max_header_list_size, qpack_max_table_capacity, qpack_blocked_streams)?;
+ write!(f, "SETTINGS max_field_section={:?}, qpack_max_table={:?}, qpack_blocked={:?} raw={:?}", max_field_section_size, qpack_max_table_capacity, qpack_blocked_streams, raw)?;
},
Frame::PushPromise {
@@ -287,8 +452,32 @@ impl std::fmt::Debug for Frame {
write!(f, "MAX_PUSH_ID push_id={}", push_id)?;
},
- Frame::Unknown => {
- write!(f, "UNKNOWN")?;
+ Frame::PriorityUpdateRequest {
+ prioritized_element_id,
+ priority_field_value,
+ } => {
+ write!(
+ f,
+ "PRIORITY_UPDATE request_stream_id={}, priority_field_len={}",
+ prioritized_element_id,
+ priority_field_value.len()
+ )?;
+ },
+
+ Frame::PriorityUpdatePush {
+ prioritized_element_id,
+ priority_field_value,
+ } => {
+ write!(
+ f,
+ "PRIORITY_UPDATE push_id={}, priority_field_len={}",
+ prioritized_element_id,
+ priority_field_value.len()
+ )?;
+ },
+
+ Frame::Unknown { raw_type, .. } => {
+ write!(f, "UNKNOWN raw_type={}", raw_type,)?;
},
}
@@ -299,10 +488,11 @@ impl std::fmt::Debug for Frame {
fn parse_settings_frame(
b: &mut octets::Octets, settings_length: usize,
) -> Result<Frame> {
- let mut max_header_list_size = None;
+ let mut max_field_section_size = None;
let mut qpack_max_table_capacity = None;
let mut qpack_blocked_streams = None;
let mut h3_datagram = None;
+ let mut raw = Vec::new();
// Reject SETTINGS frames that are too long.
if settings_length > MAX_SETTINGS_PAYLOAD_SIZE {
@@ -310,28 +500,32 @@ fn parse_settings_frame(
}
while b.off() < settings_length {
- let setting_ty = b.get_varint()?;
- let settings_val = b.get_varint()?;
+ let identifier = b.get_varint()?;
+ let value = b.get_varint()?;
+
+ // MAX_SETTINGS_PAYLOAD_SIZE protects us from storing too many raw
+ // settings.
+ raw.push((identifier, value));
- match setting_ty {
+ match identifier {
SETTINGS_QPACK_MAX_TABLE_CAPACITY => {
- qpack_max_table_capacity = Some(settings_val);
+ qpack_max_table_capacity = Some(value);
},
- SETTINGS_MAX_HEADER_LIST_SIZE => {
- max_header_list_size = Some(settings_val);
+ SETTINGS_MAX_FIELD_SECTION_SIZE => {
+ max_field_section_size = Some(value);
},
SETTINGS_QPACK_BLOCKED_STREAMS => {
- qpack_blocked_streams = Some(settings_val);
+ qpack_blocked_streams = Some(value);
},
SETTINGS_H3_DATAGRAM => {
- if settings_val > 1 {
+ if value > 1 {
return Err(super::Error::SettingsError);
}
- h3_datagram = Some(settings_val);
+ h3_datagram = Some(value);
},
// Reserved values overlap with HTTP/2 and MUST be rejected
@@ -344,11 +538,12 @@ fn parse_settings_frame(
}
Ok(Frame::Settings {
- max_header_list_size,
+ max_field_section_size,
qpack_max_table_capacity,
qpack_blocked_streams,
h3_datagram,
grease: None,
+ raw: Some(raw),
})
}
@@ -365,6 +560,31 @@ fn parse_push_promise(
})
}
+fn parse_priority_update(
+ frame_type: u64, payload_length: u64, b: &mut octets::Octets,
+) -> Result<Frame> {
+ let prioritized_element_id = b.get_varint()?;
+ let priority_field_value_length =
+ payload_length - octets::varint_len(prioritized_element_id) as u64;
+ let priority_field_value =
+ b.get_bytes(priority_field_value_length as usize)?.to_vec();
+
+ match frame_type {
+ PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID =>
+ Ok(Frame::PriorityUpdateRequest {
+ prioritized_element_id,
+ priority_field_value,
+ }),
+
+ PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID => Ok(Frame::PriorityUpdatePush {
+ prioritized_element_id,
+ priority_field_value,
+ }),
+
+ _ => unreachable!(),
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -456,12 +676,20 @@ mod tests {
fn settings_all_no_grease() {
let mut d = [42; 128];
+ let raw_settings = vec![
+ (SETTINGS_MAX_FIELD_SECTION_SIZE, 0),
+ (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),
+ (SETTINGS_QPACK_BLOCKED_STREAMS, 0),
+ (SETTINGS_H3_DATAGRAM, 0),
+ ];
+
let frame = Frame::Settings {
- max_header_list_size: Some(0),
+ max_field_section_size: Some(0),
qpack_max_table_capacity: Some(0),
qpack_blocked_streams: Some(0),
h3_datagram: Some(0),
grease: None,
+ raw: Some(raw_settings),
};
let frame_payload_len = 9;
@@ -490,20 +718,31 @@ mod tests {
let mut d = [42; 128];
let frame = Frame::Settings {
- max_header_list_size: Some(0),
+ max_field_section_size: Some(0),
qpack_max_table_capacity: Some(0),
qpack_blocked_streams: Some(0),
h3_datagram: Some(0),
grease: Some((33, 33)),
+ raw: Default::default(),
};
- // Frame parsing will always ignore GREASE values.
+ let raw_settings = vec![
+ (SETTINGS_MAX_FIELD_SECTION_SIZE, 0),
+ (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),
+ (SETTINGS_QPACK_BLOCKED_STREAMS, 0),
+ (SETTINGS_H3_DATAGRAM, 0),
+ (33, 33),
+ ];
+
+ // Frame parsing will not populate GREASE property but will be in the
+ // raw info.
let frame_parsed = Frame::Settings {
- max_header_list_size: Some(0),
+ max_field_section_size: Some(0),
qpack_max_table_capacity: Some(0),
qpack_blocked_streams: Some(0),
h3_datagram: Some(0),
grease: None,
+ raw: Some(raw_settings),
};
let frame_payload_len = 11;
@@ -531,12 +770,15 @@ mod tests {
fn settings_h3_only() {
let mut d = [42; 128];
+ let raw_settings = vec![(SETTINGS_MAX_FIELD_SECTION_SIZE, 1024)];
+
let frame = Frame::Settings {
- max_header_list_size: Some(1024),
+ max_field_section_size: Some(1024),
qpack_max_table_capacity: None,
qpack_blocked_streams: None,
h3_datagram: None,
grease: None,
+ raw: Some(raw_settings),
};
let frame_payload_len = 3;
@@ -564,12 +806,15 @@ mod tests {
fn settings_h3_dgram_only() {
let mut d = [42; 128];
+ let raw_settings = vec![(SETTINGS_H3_DATAGRAM, 1)];
+
let frame = Frame::Settings {
- max_header_list_size: None,
+ max_field_section_size: None,
qpack_max_table_capacity: None,
qpack_blocked_streams: None,
h3_datagram: Some(1),
grease: None,
+ raw: Some(raw_settings),
};
let frame_payload_len = 3;
@@ -598,11 +843,12 @@ mod tests {
let mut d = [42; 128];
let frame = Frame::Settings {
- max_header_list_size: None,
+ max_field_section_size: None,
qpack_max_table_capacity: None,
qpack_blocked_streams: None,
h3_datagram: Some(5),
grease: None,
+ raw: Default::default(),
};
let frame_payload_len = 3;
@@ -629,12 +875,18 @@ mod tests {
fn settings_qpack_only() {
let mut d = [42; 128];
+ let raw_settings = vec![
+ (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),
+ (SETTINGS_QPACK_BLOCKED_STREAMS, 0),
+ ];
+
let frame = Frame::Settings {
- max_header_list_size: None,
+ max_field_section_size: None,
qpack_max_table_capacity: Some(0),
qpack_blocked_streams: Some(0),
h3_datagram: None,
grease: None,
+ raw: Some(raw_settings),
};
let frame_payload_len = 4;
@@ -837,9 +1089,79 @@ mod tests {
}
#[test]
+ fn priority_update_request() {
+ let mut d = [42; 128];
+
+ let prioritized_element_id = 4;
+ let priority_field_value = b"abcdefghijklm".to_vec();
+ let frame_payload_len = 1 + priority_field_value.len();
+ let frame_header_len = 5;
+
+ let frame = Frame::PriorityUpdateRequest {
+ prioritized_element_id,
+ priority_field_value,
+ };
+
+ let wire_len = {
+ let mut b = octets::OctetsMut::with_slice(&mut d);
+ frame.to_bytes(&mut b).unwrap()
+ };
+
+ assert_eq!(wire_len, frame_header_len + frame_payload_len);
+
+ assert_eq!(
+ Frame::from_bytes(
+ PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID,
+ frame_payload_len as u64,
+ &d[frame_header_len..]
+ )
+ .unwrap(),
+ frame
+ );
+ }
+
+ #[test]
+ fn priority_update_push() {
+ let mut d = [42; 128];
+
+ let prioritized_element_id = 6;
+ let priority_field_value = b"abcdefghijklm".to_vec();
+ let frame_payload_len = 1 + priority_field_value.len();
+ let frame_header_len = 5;
+
+ let frame = Frame::PriorityUpdatePush {
+ prioritized_element_id,
+ priority_field_value,
+ };
+
+ let wire_len = {
+ let mut b = octets::OctetsMut::with_slice(&mut d);
+ frame.to_bytes(&mut b).unwrap()
+ };
+
+ assert_eq!(wire_len, frame_header_len + frame_payload_len);
+
+ assert_eq!(
+ Frame::from_bytes(
+ PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID,
+ frame_payload_len as u64,
+ &d[frame_header_len..]
+ )
+ .unwrap(),
+ frame
+ );
+ }
+
+ #[test]
fn unknown_type() {
let d = [42; 12];
- assert_eq!(Frame::from_bytes(255, 12345, &d[..]), Ok(Frame::Unknown));
+ assert_eq!(
+ Frame::from_bytes(255, 12345, &d[..]),
+ Ok(Frame::Unknown {
+ raw_type: 255,
+ payload_length: 12345
+ })
+ );
}
}