Skip to content

Commit

Permalink
Replace custom RecvError with std::io::Error
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
msk committed May 9, 2024
1 parent 62c56c8 commit 29ad689
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 36 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
121 changes: 98 additions & 23 deletions src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,30 @@
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
/// header.
///
/// # 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<u8>) -> Result<T, RecvError>
/// 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<u8>) -> io::Result<T>
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}"),
)

Check warning on line 28 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L25-L28

Added lines #L25 - L28 were not covered by tests
})
}

/// Receives a sequence of bytes with a big-endian 4-byte length header.
Expand All @@ -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<u8>,
) -> 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<u8>) -> io::Result<()> {
let mut len_buf = [0; mem::size_of::<u32>()];
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));

Check warning on line 43 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L43

Added line #L43 was not covered by tests
}
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

Check warning on line 49 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L49

Added line #L49 was not covered by tests
.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),

Check warning on line 56 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L53-L56

Added lines #L53 - L56 were not covered by tests
}
}

Check warning on line 58 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L58

Added line #L58 was not covered by tests

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"),

Check warning on line 66 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L60-L66

Added lines #L60 - L66 were not covered by tests
ReadError::IllegalOrderedRead => {
io::Error::new(io::ErrorKind::InvalidInput, "illegal ordered read")

Check warning on line 68 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L68

Added line #L68 was not covered by tests
}
ReadError::ZeroRttRejected => {
io::Error::new(io::ErrorKind::ConnectionRefused, "0-RTT rejected")

Check warning on line 71 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L71

Added line #L71 was not covered by tests
}
}
}

Check warning on line 74 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L74

Added line #L74 was not covered by tests

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),

Check warning on line 91 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L76-L91

Added lines #L76 - L91 were not covered by tests
ConnectionError::LocallyClosed => {
io::Error::new(io::ErrorKind::Other, "connection locally closed")

Check warning on line 93 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L93

Added line #L93 was not covered by tests
}
}
}

Check warning on line 96 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L96

Added line #L96 was not covered by tests

fn from_transport_error_to_io_error(e: quinn_proto::TransportError) -> io::Error {
use quinn_proto::TransportErrorCode;

match e.code {

Check warning on line 101 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L98-L101

Added lines #L98 - L101 were not covered by tests
TransportErrorCode::CONNECTION_REFUSED => {
io::Error::new(io::ErrorKind::ConnectionRefused, e.reason)

Check warning on line 103 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L103

Added line #L103 was not covered by tests
}
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)

Check warning on line 115 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L115

Added line #L115 was not covered by tests
}
TransportErrorCode::NO_VIABLE_PATH => {
// TODO: Use `io::ErrorKind::HostUnreachable` when it is stabilized
io::Error::new(io::ErrorKind::Other, e.reason)

Check warning on line 119 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L119

Added line #L119 was not covered by tests
}
_ => {
// * 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)

Check warning on line 127 in src/frame.rs

View check run for this annotation

Codecov / codecov/patch

src/frame.rs#L127

Added line #L127 was not covered by tests
}
}
}

/// The error type for sending a message as a frame.
Expand Down
17 changes: 8 additions & 9 deletions src/message.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -13,22 +13,21 @@ 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
///
/// * panic if it failed to convert 4 byte data to u32 value
pub async fn recv_request_raw<'b>(
recv: &mut RecvStream,
buf: &'b mut Vec<u8>,
) -> Result<(u32, &'b [u8]), RecvError> {
) -> io::Result<(u32, &'b [u8])> {
frame::recv_raw(recv, buf).await?;
if buf.len() < mem::size_of::<u32>() {
return Err(RecvError::DeserializationFailure(Box::new(
bincode::ErrorKind::SizeLimit,
)));
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"message too short to contain a code",
));

Check warning on line 30 in src/message.rs

View check run for this annotation

Codecov / codecov/patch

src/message.rs#L27-L30

Added lines #L27 - L30 were not covered by tests
}
let code = u32::from_le_bytes(buf[..mem::size_of::<u32>()].try_into().expect("4 bytes"));
Ok((code, buf[mem::size_of::<u32>()..].as_ref()))
Expand Down
14 changes: 10 additions & 4 deletions src/request.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Helper functions for request handlers.
use std::io;

use crate::{frame, message};
use bincode::Options;
use quinn::SendStream;
Expand All @@ -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<T, frame::RecvError> {
/// Returns an error if the arguments could not be deserialized.
pub fn parse_args<'de, T: Deserialize<'de>>(args: &'de [u8]) -> io::Result<T> {

Check warning on line 15 in src/request.rs

View check run for this annotation

Codecov / codecov/patch

src/request.rs#L15

Added line #L15 was not covered by tests
bincode::DefaultOptions::new()
.deserialize::<T>(args)
.map_err(frame::RecvError::DeserializationFailure)
.map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("failed deserializing message: {e}"),
)
})

Check warning on line 23 in src/request.rs

View check run for this annotation

Codecov / codecov/patch

src/request.rs#L18-L23

Added lines #L18 - L23 were not covered by tests
}

/// Sends a response to a request.
Expand Down

0 comments on commit 29ad689

Please sign in to comment.