Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace custom RecvError with std::io::Error #75

Merged
merged 1 commit into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
///
/// # 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 @@
///
/// # 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 @@
///
/// # 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