diff options
Diffstat (limited to 'src/h3/frame.rs')
-rw-r--r-- | src/h3/frame.rs | 202 |
1 files changed, 200 insertions, 2 deletions
diff --git a/src/h3/frame.rs b/src/h3/frame.rs index bee2b3b..085524b 100644 --- a/src/h3/frame.rs +++ b/src/h3/frame.rs @@ -39,6 +39,10 @@ pub const MAX_PUSH_FRAME_TYPE_ID: u64 = 0xD; 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; + +// Permit between 16 maximally-encoded and 128 minimally-encoded SETTINGS. +const MAX_SETTINGS_PAYLOAD_SIZE: usize = 256; #[derive(Clone, PartialEq)] pub enum Frame { @@ -58,6 +62,7 @@ pub enum Frame { max_header_list_size: Option<u64>, qpack_max_table_capacity: Option<u64>, qpack_blocked_streams: Option<u64>, + h3_datagram: Option<u64>, grease: Option<(u64, u64)>, }, @@ -146,6 +151,7 @@ impl Frame { max_header_list_size, qpack_max_table_capacity, qpack_blocked_streams, + h3_datagram, grease, } => { let mut len = 0; @@ -165,6 +171,11 @@ impl Frame { len += octets::varint_len(*val); } + if let Some(val) = h3_datagram { + len += octets::varint_len(SETTINGS_H3_DATAGRAM); + len += octets::varint_len(*val); + } + if let Some(val) = grease { len += octets::varint_len(val.0); len += octets::varint_len(val.1); @@ -188,6 +199,11 @@ impl Frame { b.put_varint(*val as u64)?; } + if let Some(val) = h3_datagram { + b.put_varint(SETTINGS_H3_DATAGRAM)?; + b.put_varint(*val as u64)?; + } + if let Some(val) = grease { b.put_varint(val.0)?; b.put_varint(val.1)?; @@ -286,6 +302,12 @@ fn parse_settings_frame( let mut max_header_list_size = None; let mut qpack_max_table_capacity = None; let mut qpack_blocked_streams = None; + let mut h3_datagram = None; + + // Reject SETTINGS frames that are too long. + if settings_length > MAX_SETTINGS_PAYLOAD_SIZE { + return Err(super::Error::ExcessiveLoad); + } while b.off() < settings_length { let setting_ty = b.get_varint()?; @@ -304,6 +326,18 @@ fn parse_settings_frame( qpack_blocked_streams = Some(settings_val); }, + SETTINGS_H3_DATAGRAM => { + if settings_val > 1 { + return Err(super::Error::SettingsError); + } + + h3_datagram = Some(settings_val); + }, + + // Reserved values overlap with HTTP/2 and MUST be rejected + 0x0 | 0x2 | 0x3 | 0x4 | 0x5 => + return Err(super::Error::SettingsError), + // Unknown Settings parameters must be ignored. _ => (), } @@ -313,6 +347,7 @@ fn parse_settings_frame( max_header_list_size, qpack_max_table_capacity, qpack_blocked_streams, + h3_datagram, grease: None, }) } @@ -425,10 +460,11 @@ mod tests { max_header_list_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + h3_datagram: Some(0), grease: None, }; - let frame_payload_len = 6; + let frame_payload_len = 9; let frame_header_len = 2; let wire_len = { @@ -457,6 +493,7 @@ mod tests { max_header_list_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + h3_datagram: Some(0), grease: Some((33, 33)), }; @@ -465,10 +502,11 @@ mod tests { max_header_list_size: Some(0), qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + h3_datagram: Some(0), grease: None, }; - let frame_payload_len = 8; + let frame_payload_len = 11; let frame_header_len = 2; let wire_len = { @@ -497,6 +535,7 @@ mod tests { max_header_list_size: Some(1024), qpack_max_table_capacity: None, qpack_blocked_streams: None, + h3_datagram: None, grease: None, }; @@ -522,6 +561,71 @@ mod tests { } #[test] + fn settings_h3_dgram_only() { + let mut d = [42; 128]; + + let frame = Frame::Settings { + max_header_list_size: None, + qpack_max_table_capacity: None, + qpack_blocked_streams: None, + h3_datagram: Some(1), + grease: None, + }; + + let frame_payload_len = 3; + let frame_header_len = 2; + + 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( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len as u64, + &d[frame_header_len..] + ) + .unwrap(), + frame + ); + } + + #[test] + fn settings_h3_dgram_bad() { + let mut d = [42; 128]; + + let frame = Frame::Settings { + max_header_list_size: None, + qpack_max_table_capacity: None, + qpack_blocked_streams: None, + h3_datagram: Some(5), + grease: None, + }; + + let frame_payload_len = 3; + let frame_header_len = 2; + + 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( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len as u64, + &d[frame_header_len..] + ), + Err(crate::h3::Error::SettingsError) + ); + } + + #[test] fn settings_qpack_only() { let mut d = [42; 128]; @@ -529,6 +633,7 @@ mod tests { max_header_list_size: None, qpack_max_table_capacity: Some(0), qpack_blocked_streams: Some(0), + h3_datagram: None, grease: None, }; @@ -554,6 +659,99 @@ mod tests { } #[test] + fn settings_h2_prohibited() { + // We need to test the prohibited values (0x0 | 0x2 | 0x3 | 0x4 | 0x5) + // but the quiche API doesn't support that, so use a manually created + // frame data buffer where d[frame_header_len] is the SETTING type field. + let frame_payload_len = 2u64; + let frame_header_len = 2; + let mut d = [ + SETTINGS_FRAME_TYPE_ID as u8, + frame_payload_len as u8, + 0x0, + 1, + ]; + + assert_eq!( + Frame::from_bytes( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len, + &d[frame_header_len..] + ), + Err(crate::h3::Error::SettingsError) + ); + + d[frame_header_len] = 0x2; + + assert_eq!( + Frame::from_bytes( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len, + &d[frame_header_len..] + ), + Err(crate::h3::Error::SettingsError) + ); + + d[frame_header_len] = 0x3; + + assert_eq!( + Frame::from_bytes( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len, + &d[frame_header_len..] + ), + Err(crate::h3::Error::SettingsError) + ); + + d[frame_header_len] = 0x4; + + assert_eq!( + Frame::from_bytes( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len, + &d[frame_header_len..] + ), + Err(crate::h3::Error::SettingsError) + ); + + d[frame_header_len] = 0x5; + + assert_eq!( + Frame::from_bytes( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len, + &d[frame_header_len..] + ), + Err(crate::h3::Error::SettingsError) + ); + } + + #[test] + fn settings_too_big() { + // We need to test a SETTINGS frame that exceeds + // MAX_SETTINGS_PAYLOAD_SIZE, so just craft a special buffer that look + // likes the frame. The payload content doesn't matter since quiche + // should abort before then. + let frame_payload_len = MAX_SETTINGS_PAYLOAD_SIZE + 1; + let frame_header_len = 2; + let d = [ + SETTINGS_FRAME_TYPE_ID as u8, + frame_payload_len as u8, + 0x1, + 1, + ]; + + assert_eq!( + Frame::from_bytes( + SETTINGS_FRAME_TYPE_ID, + frame_payload_len as u64, + &d[frame_header_len..] + ), + Err(crate::h3::Error::ExcessiveLoad) + ); + } + + #[test] fn push_promise() { let mut d = [42; 128]; |