diff --git a/quinn-h3/Cargo.toml b/quinn-h3/Cargo.toml index 6756f4275..ba55edb61 100644 --- a/quinn-h3/Cargo.toml +++ b/quinn-h3/Cargo.toml @@ -32,6 +32,7 @@ bitlab = "0.8.1" lazy_static = "1" err-derive = "0.1.5" slog = "2.1" +http = "0.1" [dev-dependencies] proptest = "0.9.1" diff --git a/quinn-h3/src/proto/connection.rs b/quinn-h3/src/proto/connection.rs index e72d638a0..023ff2850 100644 --- a/quinn-h3/src/proto/connection.rs +++ b/quinn-h3/src/proto/connection.rs @@ -1,11 +1,119 @@ +use bytes::BytesMut; +use http::HeaderMap; +use quinn_proto::StreamId; + +use crate::proto::frame::HeadersFrame; +use crate::qpack::{self, DynamicTable, EncoderError, HeaderField}; use crate::Settings; pub struct Connection { - settings: Settings, + #[allow(dead_code)] + local_settings: Settings, + remote_settings: Option, + decoder_table: DynamicTable, + encoder_table: DynamicTable, + pending_encoder: BytesMut, } impl Connection { pub fn new(settings: Settings) -> Self { - Self { settings } + Self { + local_settings: settings, + remote_settings: None, + decoder_table: DynamicTable::new(), + encoder_table: DynamicTable::new(), + pending_encoder: BytesMut::with_capacity(2048), + } + } + + pub fn encode_header( + &mut self, + stream_id: &StreamId, + headers: &HeaderMap, + ) -> Result { + if let Some(ref s) = self.remote_settings { + if headers.len() as u64 > s.max_header_list_size { + return Err(Error::HeaderListTooLarge); + } + } + + let headers = headers.into_iter().map(HeaderField::from); + + let mut block = BytesMut::with_capacity(512); + qpack::encode( + &mut self.encoder_table.encoder(stream_id.0), + &mut block, + &mut self.pending_encoder, + headers, + )?; + + Ok(HeadersFrame { + encoded: block.into(), + }) + } +} + +#[derive(Debug, PartialEq)] +pub enum Error { + HeaderListTooLarge, + EncodeError { reason: EncoderError }, +} + +impl From for Error { + fn from(err: EncoderError) -> Error { + Error::EncodeError { reason: err } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use http::header::{HeaderMap, HeaderValue}; + + #[test] + fn encode_no_dynamic() { + let mut header = HeaderMap::new(); + header.append("hello", HeaderValue::from_static("text/html")); + + let mut conn = Connection::new(Settings::default()); + let mut conn = Connection::default(); + assert_matches!(conn.encode_header(&StreamId(1), &header), Ok(_)); + + assert!(conn.pending_encoder.is_empty()); + } + + #[test] + fn encode_with_dynamic() { + let mut header = HeaderMap::new(); + header.append("hello", HeaderValue::from_static("text/html")); + + let mut conn = Connection::new(Settings::default()); + conn.encoder_table + .inserter() + .set_max_mem_size(2048) + .expect("set table size"); + conn.encoder_table + .set_max_blocked(12usize) + .expect("set max blocked"); + assert_matches!(conn.encode_header(&StreamId(1), &header), Ok(_)); + assert!(!conn.pending_encoder.is_empty()); + } + + #[test] + fn encode_too_many_fields() { + let mut header = HeaderMap::new(); + for _ in 0..5 { + header.append("hello", HeaderValue::from_static("text/html")); + } + + let mut conn = Connection::new(Settings::default()); + conn.remote_settings = Some(Settings { + max_header_list_size: 4, + ..Settings::default() + }); + assert_eq!( + conn.encode_header(&StreamId(1), &header), + Err(Error::HeaderListTooLarge) + ); } }