diff --git a/src/error.rs b/src/error.rs index e11aef3..0f00064 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ //! Error handling. -use std::{borrow::Cow, error::Error as ErrorTrait, fmt, io, result, str, string}; +use std::{error::Error as ErrorTrait, fmt, io, result, str, string}; use crate::protocol::{frame::coding::Data, Message}; use http::Response; @@ -46,7 +46,7 @@ pub enum Error { /// - When reading: buffer capacity exhausted. /// - When writing: your message is bigger than the configured max message size /// (64MB by default). - Capacity(Cow<'static, str>), + Capacity(CapacityErrorType), /// Protocol violation. Protocol(ProtocolErrorType), /// Message send queue full. @@ -146,38 +146,39 @@ impl From for Error { impl From for Error { fn from(err: httparse::Error) -> Self { match err { - httparse::Error::TooManyHeaders => Error::Capacity("Too many headers".into()), + httparse::Error::TooManyHeaders => Error::Capacity(CapacityErrorType::TooManyHeaders), e => Error::Protocol(ProtocolErrorType::HttparseError(e)), } } } -/// Indicates the specific type/cause of URL error. -#[derive(Debug, PartialEq, Eq)] -pub enum UrlErrorType { - /// TLS is used despite not being compiled with the TLS feature enabled. - TlsFeatureNotEnabled, - /// The URL does not include a host name. - NoHostName, - /// Failed to connect with this URL. - UnableToConnect(String), - /// Unsupported URL scheme used (only `ws://` or `wss://` may be used). - UnsupportedUrlScheme, - /// The URL host name, though included, is empty. - EmptyHostName, - /// The URL does not include a path/query. - NoPathOrQuery, +/// Indicates the specific type/cause of a capacity error. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum CapacityErrorType { + /// Too many headers provided (see [`httparse::Error::TooManyHeaders`]). + TooManyHeaders, + /// Received header is too long. + HeaderTooLong, + /// Message is bigger than the maximum allowed size. + MessageTooLong { + /// The size of the message. + size: usize, + /// The maximum allowed message size. + max_size: usize, + }, + /// TCP buffer is full. + TcpBufferFull, } -impl fmt::Display for UrlErrorType { +impl fmt::Display for CapacityErrorType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - UrlErrorType::TlsFeatureNotEnabled => write!(f, "TLS support not compiled in"), - UrlErrorType::NoHostName => write!(f, "No host name in the URL"), - UrlErrorType::UnableToConnect(uri) => write!(f, "Unable to connect to {}", uri), - UrlErrorType::UnsupportedUrlScheme => write!(f, "URL scheme not supported"), - UrlErrorType::EmptyHostName => write!(f, "URL contains empty host name"), - UrlErrorType::NoPathOrQuery => write!(f, "No path/query in URL"), + CapacityErrorType::TooManyHeaders => write!(f, "Too many headers"), + CapacityErrorType::HeaderTooLong => write!(f, "Header too long"), + CapacityErrorType::MessageTooLong { size, max_size } => { + write!(f, "Message too long: {} > {}", size, max_size) + } + CapacityErrorType::TcpBufferFull => write!(f, "Incoming TCP buffer is full"), } } } @@ -302,3 +303,33 @@ impl fmt::Display for ProtocolErrorType { } } } + +/// Indicates the specific type/cause of URL error. +#[derive(Debug, PartialEq, Eq)] +pub enum UrlErrorType { + /// TLS is used despite not being compiled with the TLS feature enabled. + TlsFeatureNotEnabled, + /// The URL does not include a host name. + NoHostName, + /// Failed to connect with this URL. + UnableToConnect(String), + /// Unsupported URL scheme used (only `ws://` or `wss://` may be used). + UnsupportedUrlScheme, + /// The URL host name, though included, is empty. + EmptyHostName, + /// The URL does not include a path/query. + NoPathOrQuery, +} + +impl fmt::Display for UrlErrorType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UrlErrorType::TlsFeatureNotEnabled => write!(f, "TLS support not compiled in"), + UrlErrorType::NoHostName => write!(f, "No host name in the URL"), + UrlErrorType::UnableToConnect(uri) => write!(f, "Unable to connect to {}", uri), + UrlErrorType::UnsupportedUrlScheme => write!(f, "URL scheme not supported"), + UrlErrorType::EmptyHostName => write!(f, "URL contains empty host name"), + UrlErrorType::NoPathOrQuery => write!(f, "No path/query in URL"), + } + } +} diff --git a/src/handshake/machine.rs b/src/handshake/machine.rs index e23fb15..05d3a6d 100644 --- a/src/handshake/machine.rs +++ b/src/handshake/machine.rs @@ -3,7 +3,7 @@ use log::*; use std::io::{Cursor, Read, Write}; use crate::{ - error::{Error, ProtocolErrorType, Result}, + error::{CapacityErrorType, Error, ProtocolErrorType, Result}, util::NonBlockingResult, }; use input_buffer::{InputBuffer, MIN_READ}; @@ -46,7 +46,7 @@ impl HandshakeMachine { let read = buf .prepare_reserve(MIN_READ) .with_limit(usize::max_value()) // TODO limit size - .map_err(|_| Error::Capacity("Header too long".into()))? + .map_err(|_| Error::Capacity(CapacityErrorType::HeaderTooLong))? .read_from(&mut self.stream) .no_block()?; match read { diff --git a/src/protocol/frame/mod.rs b/src/protocol/frame/mod.rs index dfd0bd5..c435762 100644 --- a/src/protocol/frame/mod.rs +++ b/src/protocol/frame/mod.rs @@ -8,7 +8,7 @@ mod mask; pub use self::frame::{CloseFrame, Frame, FrameHeader}; -use crate::error::{Error, Result}; +use crate::error::{CapacityErrorType, Error, Result}; use input_buffer::{InputBuffer, MIN_READ}; use log::*; use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read, Write}; @@ -133,9 +133,10 @@ impl FrameCodec { // Enforce frame size limit early and make sure `length` // is not too big (fits into `usize`). if length > max_size as u64 { - return Err(Error::Capacity( - format!("Message length too big: {} > {}", length, max_size).into(), - )); + return Err(Error::Capacity(CapacityErrorType::MessageTooLong { + size: length as usize, + max_size, + })); } let input_size = cursor.get_ref().len() as u64 - cursor.position(); @@ -155,7 +156,7 @@ impl FrameCodec { .in_buffer .prepare_reserve(MIN_READ) .with_limit(usize::max_value()) - .map_err(|_| Error::Capacity("Incoming TCP buffer is full".into()))? + .map_err(|_| Error::Capacity(CapacityErrorType::TcpBufferFull))? .read_from(stream)?; if size == 0 { trace!("no frame received"); @@ -206,6 +207,8 @@ impl FrameCodec { #[cfg(test)] mod tests { + use crate::error::{CapacityErrorType, Error}; + use super::{Frame, FrameSocket}; use std::io::Cursor; @@ -266,9 +269,9 @@ mod tests { fn size_limit_hit() { let raw = Cursor::new(vec![0x82, 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]); let mut sock = FrameSocket::new(raw); - assert_eq!( - sock.read_frame(Some(5)).unwrap_err().to_string(), - "Space limit exceeded: Message length too big: 7 > 5" - ); + match sock.read_frame(Some(5)) { + Err(Error::Capacity(CapacityErrorType::MessageTooLong { size: 7, max_size: 5 })) => {} + _ => panic!(), + } } } diff --git a/src/protocol/message.rs b/src/protocol/message.rs index f799dbf..1e9ce42 100644 --- a/src/protocol/message.rs +++ b/src/protocol/message.rs @@ -6,7 +6,7 @@ use std::{ }; use super::frame::CloseFrame; -use crate::error::{Error, Result}; +use crate::error::{CapacityErrorType, Error, Result}; mod string_collect { use utf8::DecodeError; @@ -122,9 +122,10 @@ impl IncompleteMessage { let portion_size = tail.as_ref().len(); // Be careful about integer overflows here. if my_size > max_size || portion_size > max_size - my_size { - return Err(Error::Capacity( - format!("Message too big: {} + {} > {}", my_size, portion_size, max_size).into(), - )); + return Err(Error::Capacity(CapacityErrorType::MessageTooLong { + size: my_size + portion_size, + max_size, + })); } match self.collector { diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index be002e8..21465b5 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -669,6 +669,7 @@ impl CheckConnectionReset for Result { #[cfg(test)] mod tests { use super::{Message, Role, WebSocket, WebSocketConfig}; + use crate::error::{CapacityErrorType, Error}; use std::{io, io::Cursor}; @@ -711,10 +712,11 @@ mod tests { ]); let limit = WebSocketConfig { max_message_size: Some(10), ..WebSocketConfig::default() }; let mut socket = WebSocket::from_raw_socket(WriteMoc(incoming), Role::Client, Some(limit)); - assert_eq!( - socket.read_message().unwrap_err().to_string(), - "Space limit exceeded: Message too big: 7 + 6 > 10" - ); + + match socket.read_message() { + Err(Error::Capacity(CapacityErrorType::MessageTooLong { size: 13, max_size: 10 })) => {} + _ => panic!(), + } } #[test] @@ -722,9 +724,10 @@ mod tests { let incoming = Cursor::new(vec![0x82, 0x03, 0x01, 0x02, 0x03]); let limit = WebSocketConfig { max_message_size: Some(2), ..WebSocketConfig::default() }; let mut socket = WebSocket::from_raw_socket(WriteMoc(incoming), Role::Client, Some(limit)); - assert_eq!( - socket.read_message().unwrap_err().to_string(), - "Space limit exceeded: Message too big: 0 + 3 > 2" - ); + + match socket.read_message() { + Err(Error::Capacity(CapacityErrorType::MessageTooLong { size: 3, max_size: 2 })) => {} + _ => panic!(), + } } }