diff --git a/src/codec/framed_write.rs b/src/codec/framed_write.rs index b9a06c6..c63f122 100644 --- a/src/codec/framed_write.rs +++ b/src/codec/framed_write.rs @@ -271,6 +271,11 @@ impl FramedWrite { self.max_frame_size = val as FrameSize; } + /// Set the peer's header table size. + pub fn set_header_table_size(&mut self, val: usize) { + self.hpack.update_max_size(val); + } + /// Retrieve the last data frame that has been sent pub fn take_last_data_frame(&mut self) -> Option> { self.last_data_frame.take() diff --git a/src/codec/mod.rs b/src/codec/mod.rs index 9bd4e73..7d0ab73 100644 --- a/src/codec/mod.rs +++ b/src/codec/mod.rs @@ -89,6 +89,11 @@ impl Codec { self.framed_write().set_max_frame_size(val) } + /// Set the peer's header table size size. + pub fn set_send_header_table_size(&mut self, val: usize) { + self.framed_write().set_header_table_size(val) + } + /// Set the max header list size that can be received. pub fn set_max_recv_header_list_size(&mut self, val: usize) { self.inner.set_max_header_list_size(val); diff --git a/src/frame/settings.rs b/src/frame/settings.rs index 060710d..c709381 100644 --- a/src/frame/settings.rs +++ b/src/frame/settings.rs @@ -107,6 +107,16 @@ impl Settings { self.enable_push = Some(enable as u32); } + pub fn header_table_size(&self) -> Option { + self.header_table_size + } + + /* + pub fn set_header_table_size(&mut self, size: Option) { + self.header_table_size = size; + } + */ + pub fn load(head: Head, payload: &[u8]) -> Result { use self::Setting::*; diff --git a/src/hpack/encoder.rs b/src/hpack/encoder.rs index 0be4833..ef17748 100644 --- a/src/hpack/encoder.rs +++ b/src/hpack/encoder.rs @@ -46,7 +46,6 @@ impl Encoder { /// Queues a max size update. /// /// The next call to `encode` will include a dynamic size update frame. - #[allow(dead_code)] pub fn update_max_size(&mut self, val: usize) { match self.size_update { Some(SizeUpdate::One(old)) => { diff --git a/src/proto/settings.rs b/src/proto/settings.rs index 014dbf7..b1d91e6 100644 --- a/src/proto/settings.rs +++ b/src/proto/settings.rs @@ -117,6 +117,10 @@ impl Settings { log::trace!("ACK sent; applying settings"); + if let Some(val) = settings.header_table_size() { + dst.set_send_header_table_size(val as usize); + } + if let Some(val) = settings.max_frame_size() { dst.set_max_send_frame_size(val as usize); } diff --git a/tests/h2-support/src/prelude.rs b/tests/h2-support/src/prelude.rs index b44458d..2e95b68 100644 --- a/tests/h2-support/src/prelude.rs +++ b/tests/h2-support/src/prelude.rs @@ -52,6 +52,8 @@ pub use tokio::io::{AsyncRead, AsyncWrite}; pub use std::thread; pub use std::time::Duration; +pub static MAGIC_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; + // ===== Everything under here shouldn't be used ===== // TODO: work on deleting this code @@ -62,14 +64,20 @@ use std::pin::Pin; pub trait MockH2 { fn handshake(&mut self) -> &mut Self; + + fn handshake_read_settings(&mut self, settings: &[u8]) -> &mut Self; } impl MockH2 for tokio_test::io::Builder { fn handshake(&mut self) -> &mut Self { - self.write(b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") + self.handshake_read_settings(frames::SETTINGS) + } + + fn handshake_read_settings(&mut self, settings: &[u8]) -> &mut Self { + self.write(MAGIC_PREFACE) // Settings frame .write(frames::SETTINGS) - .read(frames::SETTINGS) + .read(settings) .read(frames::SETTINGS_ACK) } } diff --git a/tests/h2-tests/tests/codec_write.rs b/tests/h2-tests/tests/codec_write.rs index a21073a..2347f63 100644 --- a/tests/h2-tests/tests/codec_write.rs +++ b/tests/h2-tests/tests/codec_write.rs @@ -51,3 +51,90 @@ async fn write_continuation_frames() { join(srv, client).await; } + +#[tokio::test] +async fn client_settings_header_table_size() { + // A server sets the SETTINGS_HEADER_TABLE_SIZE to 0, test that the + // client doesn't send indexed headers. + let _ = env_logger::try_init(); + + let io = mock_io::Builder::new() + // Read SETTINGS_HEADER_TABLE_SIZE = 0 + .handshake_read_settings(&[ + 0, 0, 6, // len + 4, // type + 0, // flags + 0, 0, 0, 0, // stream id + 0, 0x1, // id = SETTINGS_HEADER_TABLE_SIZE + 0, 0, 0, 0, // value = 0 + ]) + // Write GET / (1st) + .write(&[ + 0, 0, 0x10, 1, 5, 0, 0, 0, 1, 0x82, 0x87, 0x41, 0x8B, 0x9D, 0x29, 0xAC, 0x4B, 0x8F, + 0xA8, 0xE9, 0x19, 0x97, 0x21, 0xE9, 0x84, + ]) + .write(frames::SETTINGS_ACK) + // Read response + .read(&[0, 0, 1, 1, 5, 0, 0, 0, 1, 137]) + // Write GET / (2nd, doesn't use indexed headers) + // - Sends 0x20 about size change + // - Sends :authority as literal instead of indexed + .write(&[ + 0, 0, 0x11, 1, 5, 0, 0, 0, 3, 0x20, 0x82, 0x87, 0x1, 0x8B, 0x9D, 0x29, 0xAC, 0x4B, + 0x8F, 0xA8, 0xE9, 0x19, 0x97, 0x21, 0xE9, 0x84, + ]) + .read(&[0, 0, 1, 1, 5, 0, 0, 0, 3, 137]) + .build(); + + let (mut client, mut conn) = client::handshake(io).await.expect("handshake"); + + let req1 = client.get("https://http2.akamai.com"); + conn.drive(req1).await.expect("req1"); + + let req2 = client.get("https://http2.akamai.com"); + conn.drive(req2).await.expect("req1"); +} + +#[tokio::test] +async fn server_settings_header_table_size() { + // A client sets the SETTINGS_HEADER_TABLE_SIZE to 0, test that the + // server doesn't send indexed headers. + let _ = env_logger::try_init(); + + let io = mock_io::Builder::new() + .read(MAGIC_PREFACE) + // Read SETTINGS_HEADER_TABLE_SIZE = 0 + .read(&[ + 0, 0, 6, // len + 4, // type + 0, // flags + 0, 0, 0, 0, // stream id + 0, 0x1, // id = SETTINGS_HEADER_TABLE_SIZE + 0, 0, 0, 0, // value = 0 + ]) + .write(frames::SETTINGS) + .write(frames::SETTINGS_ACK) + .read(frames::SETTINGS_ACK) + // Write GET / + .read(&[ + 0, 0, 0x10, 1, 5, 0, 0, 0, 1, 0x82, 0x87, 0x41, 0x8B, 0x9D, 0x29, 0xAC, 0x4B, 0x8F, + 0xA8, 0xE9, 0x19, 0x97, 0x21, 0xE9, 0x84, + ]) + // Read response + //.write(&[0, 0, 6, 1, 5, 0, 0, 0, 1, 136, 64, 129, 31, 129, 143]) + .write(&[0, 0, 7, 1, 5, 0, 0, 0, 1, 32, 136, 0, 129, 31, 129, 143]) + .build(); + + let mut srv = server::handshake(io).await.expect("handshake"); + + let (_req, mut stream) = srv.accept().await.unwrap().unwrap(); + + let rsp = http::Response::builder() + .status(200) + .header("a", "b") + .body(()) + .unwrap(); + stream.send_response(rsp, true).unwrap(); + + assert!(srv.accept().await.is_none()); +}