add support for configuring max frame size
- Adds `max_frame_size` to client and server builders - Pushes max_frame_size into Codec - Detects when the Codec triggers an error from a frame too big - Sends a GOAWAY when FRAME_SIZE_ERROR is encountered reading a frame
This commit is contained in:
		| @@ -170,6 +170,12 @@ impl Builder { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the max frame size of received frames. | ||||
|     pub fn max_frame_size(&mut self, max: u32) -> &mut Self { | ||||
|         self.settings.set_max_frame_size(Some(max)); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Bind an H2 client connection. | ||||
|     /// | ||||
|     /// Returns a future which resolves to the connection value once the H2 | ||||
| @@ -203,6 +209,10 @@ where | ||||
|         // Create the codec | ||||
|         let mut codec = Codec::new(io); | ||||
|  | ||||
|         if let Some(max) = self.settings.max_frame_size() { | ||||
|             codec.set_max_recv_frame_size(max as usize); | ||||
|         } | ||||
|  | ||||
|         // Send initial settings frame | ||||
|         codec | ||||
|             .buffer(self.settings.clone().into()) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| use codec::RecvError; | ||||
| use frame::{self, Frame, Kind}; | ||||
| use frame::DEFAULT_SETTINGS_HEADER_TABLE_SIZE; | ||||
| use frame::{self, Frame, Kind, Reason}; | ||||
| use frame::{DEFAULT_MAX_FRAME_SIZE, DEFAULT_SETTINGS_HEADER_TABLE_SIZE, MAX_MAX_FRAME_SIZE}; | ||||
| use frame::Reason::*; | ||||
|  | ||||
| use hpack; | ||||
| @@ -9,6 +9,8 @@ use futures::*; | ||||
|  | ||||
| use bytes::BytesMut; | ||||
|  | ||||
| use std::io; | ||||
|  | ||||
| use tokio_io::AsyncRead; | ||||
| use tokio_io::codec::length_delimited; | ||||
|  | ||||
| @@ -228,9 +230,12 @@ impl<T> FramedRead<T> { | ||||
|     } | ||||
|  | ||||
|     /// Updates the max frame size setting. | ||||
|     /// | ||||
|     /// Must be within 16,384 and 16,777,215. | ||||
|     #[cfg(feature = "unstable")] | ||||
|     #[inline] | ||||
|     pub fn set_max_frame_size(&mut self, val: usize) { | ||||
|         assert!(DEFAULT_MAX_FRAME_SIZE as usize <= val && val <= MAX_MAX_FRAME_SIZE as usize); | ||||
|         self.inner.set_max_frame_length(val) | ||||
|     } | ||||
| } | ||||
| @@ -245,7 +250,7 @@ where | ||||
|     fn poll(&mut self) -> Poll<Option<Frame>, Self::Error> { | ||||
|         loop { | ||||
|             trace!("poll"); | ||||
|             let bytes = match try_ready!(self.inner.poll()) { | ||||
|             let bytes = match try_ready!(self.inner.poll().map_err(map_err)) { | ||||
|                 Some(bytes) => bytes, | ||||
|                 None => return Ok(Async::Ready(None)), | ||||
|             }; | ||||
| @@ -258,3 +263,17 @@ where | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn map_err(err: io::Error) -> RecvError { | ||||
|     use std::error::Error; | ||||
|  | ||||
|     if let io::ErrorKind::InvalidData = err.kind() { | ||||
|         // woah, brittle... | ||||
|         // TODO: with tokio-io v0.1.4, we can check | ||||
|         // err.get_ref().is::<tokio_io::length_delimited::FrameTooBig>() | ||||
|         if err.description() == "frame size too big" { | ||||
|             return RecvError::Connection(Reason::FrameSizeError); | ||||
|         } | ||||
|     } | ||||
|     err.into() | ||||
| } | ||||
|   | ||||
| @@ -227,7 +227,7 @@ impl<T, B> FramedWrite<T, B> { | ||||
|  | ||||
|     /// Set the peer's max frame size. | ||||
|     pub fn set_max_frame_size(&mut self, val: usize) { | ||||
|         assert!(val <= frame::MAX_MAX_FRAME_SIZE); | ||||
|         assert!(val <= frame::MAX_MAX_FRAME_SIZE as usize); | ||||
|         self.max_frame_size = val as FrameSize; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -45,10 +45,12 @@ where | ||||
|             .length_field_length(3) | ||||
|             .length_adjustment(9) | ||||
|             .num_skip(0) // Don't skip the header | ||||
|             .max_frame_length(max_frame_size) | ||||
|             .new_read(framed_write); | ||||
|  | ||||
|         let inner = FramedRead::new(delimited); | ||||
|         let mut inner = FramedRead::new(delimited); | ||||
|  | ||||
|         // Use FramedRead's method since it checks the value is within range. | ||||
|         inner.set_max_frame_size(max_frame_size); | ||||
|  | ||||
|         Codec { | ||||
|             inner, | ||||
| @@ -66,7 +68,6 @@ impl<T, B> Codec<T, B> { | ||||
|     #[cfg(feature = "unstable")] | ||||
|     #[inline] | ||||
|     pub fn set_max_recv_frame_size(&mut self, val: usize) { | ||||
|         // TODO: should probably make some assertions about max frame size... | ||||
|         self.inner.set_max_frame_size(val) | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ impl Reason { | ||||
|             FlowControlError => "flow-control protocol violated", | ||||
|             SettingsTimeout => "settings ACK not received in timely manner", | ||||
|             StreamClosed => "received frame when stream half-closed", | ||||
|             FrameSizeError => "frame sent with invalid size", | ||||
|             FrameSizeError => "frame with invalid size", | ||||
|             RefusedStream => "refused stream before processing any application logic", | ||||
|             Cancel => "stream no longer needed", | ||||
|             CompressionError => "unable to maintain the header compression context", | ||||
|   | ||||
| @@ -46,7 +46,7 @@ pub const DEFAULT_MAX_FRAME_SIZE: FrameSize = 16_384; | ||||
| pub const MAX_INITIAL_WINDOW_SIZE: usize = (1 << 31) - 1; | ||||
|  | ||||
| /// MAX_FRAME_SIZE upper bound | ||||
| pub const MAX_MAX_FRAME_SIZE: usize = (1 << 24) - 1; | ||||
| pub const MAX_MAX_FRAME_SIZE: FrameSize = (1 << 24) - 1; | ||||
|  | ||||
| // ===== impl Settings ===== | ||||
|  | ||||
| @@ -78,6 +78,13 @@ impl Settings { | ||||
|         self.max_frame_size | ||||
|     } | ||||
|  | ||||
|     pub fn set_max_frame_size(&mut self, size: Option<u32>) { | ||||
|         if let Some(val) = size { | ||||
|             assert!(DEFAULT_MAX_FRAME_SIZE <= val && val <= MAX_MAX_FRAME_SIZE); | ||||
|         } | ||||
|         self.max_frame_size = size; | ||||
|     } | ||||
|  | ||||
|     pub fn load(head: Head, payload: &[u8]) -> Result<Settings, Error> { | ||||
|         use self::Setting::*; | ||||
|  | ||||
| @@ -131,7 +138,7 @@ impl Settings { | ||||
|                     settings.initial_window_size = Some(val); | ||||
|                 }, | ||||
|                 Some(MaxFrameSize(val)) => { | ||||
|                     if val < DEFAULT_MAX_FRAME_SIZE || val as usize > MAX_MAX_FRAME_SIZE { | ||||
|                     if val < DEFAULT_MAX_FRAME_SIZE || val > MAX_MAX_FRAME_SIZE { | ||||
|                         return Err(Error::InvalidSettingValue); | ||||
|                     } else { | ||||
|                         settings.max_frame_size = Some(val); | ||||
|   | ||||
| @@ -93,6 +93,10 @@ where | ||||
|         // Create the codec | ||||
|         let mut codec = Codec::new(io); | ||||
|  | ||||
|         if let Some(max) = settings.max_frame_size() { | ||||
|             codec.set_max_recv_frame_size(max as usize); | ||||
|         } | ||||
|  | ||||
|         // Send initial settings frame | ||||
|         codec | ||||
|             .buffer(settings.clone().into()) | ||||
| @@ -180,13 +184,20 @@ impl Builder { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the max frame size of received frames. | ||||
|     pub fn max_frame_size(&mut self, max: u32) -> &mut Self { | ||||
|         self.settings.set_max_frame_size(Some(max)); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Bind an H2 server connection. | ||||
|     /// | ||||
|     /// Returns a future which resolves to the connection value once the H2 | ||||
|     /// handshake has been completed. | ||||
|     pub fn handshake<T, B>(&self, io: T) -> Handshake<T, B> | ||||
|         where T: AsyncRead + AsyncWrite + 'static, | ||||
|               B: IntoBuf + 'static | ||||
|     where | ||||
|         T: AsyncRead + AsyncWrite + 'static, | ||||
|         B: IntoBuf + 'static, | ||||
|     { | ||||
|         Server::handshake2(io, self.settings.clone()) | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user