From 29ad689d76862973ecaad417150fad0349a2be03 Mon Sep 17 00:00:00 2001 From: Min Kim Date: Thu, 9 May 2024 13:34:11 -0700 Subject: [PATCH] Replace custom `RecvError` with `std::io::Error` This commit simplifies error handling by replacing the custom `RecvError` with the standard `std::io::Error`. The changes affect functions involved in receiving and parsing data, specifically in the `frame` and `message` modules. --- CHANGELOG.md | 13 ++++++ Cargo.toml | 1 + src/frame.rs | 121 +++++++++++++++++++++++++++++++++++++++---------- src/message.rs | 17 ++++--- src/request.rs | 14 ++++-- 5 files changed, 130 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a0bb4f..a0ab5a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ file is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- `frame::recv`, `frame::recv_raw`, `message::recv_request_raw`, and + `request::parse_args` returns `io::Error` instead of `RecvError`. + +### Removed + +- `RecvError` is no longer used. The read functions return `std::io::Error` + instead. + ## [0.12.0] - 2024-04-04 ### Added @@ -287,6 +299,7 @@ without relying on the content of the response. - `send_frame` and `recv_frame` to send and receive length-delimited frames. +[Unreleased]: https://github.com/petabi/oinq/compare/0.12.0...main [0.12.0]: https://github.com/petabi/oinq/compare/0.11.0...0.12.0 [0.11.0]: https://github.com/petabi/oinq/compare/0.10.0...0.11.0 [0.10.0]: https://github.com/petabi/oinq/compare/0.9.3...0.10.0 diff --git a/Cargo.toml b/Cargo.toml index 133c4bb..ecff254 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ bincode = "1" futures = "0.3" num_enum = "0.7" quinn = "0.10" +quinn-proto = "0.10" serde = { version = "1", features = ["derive"] } thiserror = "1" tokio = "1" diff --git a/src/frame.rs b/src/frame.rs index 8bd8f98..7335e02 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -3,18 +3,9 @@ use bincode::Options; use quinn::{RecvStream, SendStream}; use serde::{Deserialize, Serialize}; -use std::mem; +use std::{io, mem}; use thiserror::Error; -/// The error type for receiving and deserializing a frame. -#[derive(Debug, Error)] -pub enum RecvError { - #[error("failed deserializing message")] - DeserializationFailure(#[from] bincode::Error), - #[error("failed to read from a stream")] - ReadError(#[from] quinn::ReadExactError), -} - /// Receives and deserializes a message with a big-endian 4-byte length header. /// /// `buf` will be filled with the message data excluding the 4-byte length @@ -22,15 +13,20 @@ pub enum RecvError { /// /// # Errors /// -/// * `RecvError::DeserializationFailure`: if the message could not be -/// deserialized -/// * `RecvError::ReadError`: if the message could not be read -pub async fn recv<'b, T>(recv: &mut RecvStream, buf: &'b mut Vec) -> Result +/// Returns an error if the message could not be read or deserialized. +pub async fn recv<'b, T>(recv: &mut RecvStream, buf: &'b mut Vec) -> io::Result where T: Deserialize<'b>, { recv_raw(recv, buf).await?; - Ok(bincode::DefaultOptions::new().deserialize(buf)?) + bincode::DefaultOptions::new() + .deserialize(buf) + .map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed deserializing message: {e}"), + ) + }) } /// Receives a sequence of bytes with a big-endian 4-byte length header. @@ -40,18 +36,97 @@ where /// /// # Errors /// -/// * `quinn::ReadExactError`: if the message could not be read -pub async fn recv_raw<'b>( - recv: &mut RecvStream, - buf: &mut Vec, -) -> Result<(), quinn::ReadExactError> { +/// Returns an error if the message could not be read. +pub async fn recv_raw<'b>(recv: &mut RecvStream, buf: &mut Vec) -> io::Result<()> { let mut len_buf = [0; mem::size_of::()]; - recv.read_exact(&mut len_buf).await?; + if let Err(e) = recv.read_exact(&mut len_buf).await { + return Err(from_read_exact_error_to_io_error(e)); + } let len = u32::from_be_bytes(len_buf) as usize; buf.resize(len, 0); - recv.read_exact(buf.as_mut_slice()).await?; - Ok(()) + recv.read_exact(buf.as_mut_slice()) + .await + .map_err(from_read_exact_error_to_io_error) +} + +fn from_read_exact_error_to_io_error(e: quinn::ReadExactError) -> io::Error { + match e { + quinn::ReadExactError::FinishedEarly => io::Error::from(io::ErrorKind::UnexpectedEof), + quinn::ReadExactError::ReadError(e) => from_read_error_to_io_error(e), + } +} + +fn from_read_error_to_io_error(e: quinn::ReadError) -> io::Error { + use quinn::ReadError; + + match e { + ReadError::Reset(_) => io::Error::from(io::ErrorKind::ConnectionReset), + ReadError::ConnectionLost(e) => from_connection_error_to_io_error(e), + ReadError::UnknownStream => io::Error::new(io::ErrorKind::NotFound, "unknown stream"), + ReadError::IllegalOrderedRead => { + io::Error::new(io::ErrorKind::InvalidInput, "illegal ordered read") + } + ReadError::ZeroRttRejected => { + io::Error::new(io::ErrorKind::ConnectionRefused, "0-RTT rejected") + } + } +} + +fn from_connection_error_to_io_error(e: quinn::ConnectionError) -> io::Error { + use quinn::ConnectionError; + + match e { + ConnectionError::VersionMismatch => io::Error::from(io::ErrorKind::ConnectionRefused), + ConnectionError::TransportError(e) => from_transport_error_to_io_error(e), + ConnectionError::ConnectionClosed(e) => io::Error::new( + io::ErrorKind::ConnectionAborted, + String::from_utf8_lossy(&e.reason), + ), + ConnectionError::ApplicationClosed(e) => io::Error::new( + io::ErrorKind::ConnectionAborted, + String::from_utf8_lossy(&e.reason), + ), + ConnectionError::Reset => io::Error::from(io::ErrorKind::ConnectionReset), + ConnectionError::TimedOut => io::Error::from(io::ErrorKind::TimedOut), + ConnectionError::LocallyClosed => { + io::Error::new(io::ErrorKind::Other, "connection locally closed") + } + } +} + +fn from_transport_error_to_io_error(e: quinn_proto::TransportError) -> io::Error { + use quinn_proto::TransportErrorCode; + + match e.code { + TransportErrorCode::CONNECTION_REFUSED => { + io::Error::new(io::ErrorKind::ConnectionRefused, e.reason) + } + TransportErrorCode::CONNECTION_ID_LIMIT_ERROR + | TransportErrorCode::CRYPTO_BUFFER_EXCEEDED + | TransportErrorCode::FINAL_SIZE_ERROR + | TransportErrorCode::FLOW_CONTROL_ERROR + | TransportErrorCode::FRAME_ENCODING_ERROR + | TransportErrorCode::INVALID_TOKEN + | TransportErrorCode::PROTOCOL_VIOLATION + | TransportErrorCode::STREAM_LIMIT_ERROR + | TransportErrorCode::STREAM_STATE_ERROR + | TransportErrorCode::TRANSPORT_PARAMETER_ERROR => { + io::Error::new(io::ErrorKind::InvalidData, e.reason) + } + TransportErrorCode::NO_VIABLE_PATH => { + // TODO: Use `io::ErrorKind::HostUnreachable` when it is stabilized + io::Error::new(io::ErrorKind::Other, e.reason) + } + _ => { + // * TransportErrorCode::AEAD_LIMIT_REACHED + // * TransportErrorCode::APPLICATION_ERROR + // * TransportErrorCode::INTERNAL_ERROR + // * TransportErrorCode::KEY_UPDATE_ERROR + // * TransportErrorCode::NO_ERROR + io::Error::new(io::ErrorKind::Other, e.reason) + } + } } /// The error type for sending a message as a frame. diff --git a/src/message.rs b/src/message.rs index 7f1fbd0..41f5200 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,10 +1,10 @@ //! Functions and errors for handling messages. -use crate::frame::{self, RecvError, SendError}; +use crate::frame::{self, SendError}; use bincode::Options; use quinn::{RecvStream, SendStream}; use serde::Serialize; -use std::{fmt, mem}; +use std::{fmt, io, mem}; /// Receives a message as a stream of bytes with a big-endian 4-byte length /// header. @@ -13,9 +13,7 @@ use std::{fmt, mem}; /// /// # Errors /// -/// * `RecvError::DeserializationFailure` if the message could not be -/// deserialized -/// * `RecvError::ReadError` if the message could not be read +/// Returns an error if the message could not be read or deserialized. /// /// # Panics /// @@ -23,12 +21,13 @@ use std::{fmt, mem}; pub async fn recv_request_raw<'b>( recv: &mut RecvStream, buf: &'b mut Vec, -) -> Result<(u32, &'b [u8]), RecvError> { +) -> io::Result<(u32, &'b [u8])> { frame::recv_raw(recv, buf).await?; if buf.len() < mem::size_of::() { - return Err(RecvError::DeserializationFailure(Box::new( - bincode::ErrorKind::SizeLimit, - ))); + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "message too short to contain a code", + )); } let code = u32::from_le_bytes(buf[..mem::size_of::()].try_into().expect("4 bytes")); Ok((code, buf[mem::size_of::()..].as_ref())) diff --git a/src/request.rs b/src/request.rs index 57a9b8e..b20fc08 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,5 +1,7 @@ //! Helper functions for request handlers. +use std::io; + use crate::{frame, message}; use bincode::Options; use quinn::SendStream; @@ -9,12 +11,16 @@ use serde::{Deserialize, Serialize}; /// /// # Errors /// -/// Returns `frame::RecvError::DeserializationFailure`: if the arguments could -/// not be deserialized. -pub fn parse_args<'de, T: Deserialize<'de>>(args: &'de [u8]) -> Result { +/// Returns an error if the arguments could not be deserialized. +pub fn parse_args<'de, T: Deserialize<'de>>(args: &'de [u8]) -> io::Result { bincode::DefaultOptions::new() .deserialize::(args) - .map_err(frame::RecvError::DeserializationFailure) + .map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed deserializing message: {e}"), + ) + }) } /// Sends a response to a request.