Skip to content

Commit

Permalink
Merge pull request #49 from fizyr/endian
Browse files Browse the repository at this point in the history
Support big endian headers in addition to little endian.
  • Loading branch information
de-vri-es authored Dec 11, 2023
2 parents b568d9b + e591af5 commit 9691a6b
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 57 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Unreleased
- [change][major] Mark `StreamConfig` and `UnixConfig` as non-exhaustive structs.
- [change][major] Make the `MessageHeader::encode/decode()` functions take an `endian` parameter.
- [add][major] Add an `endian` field to `StreamConfig` and `UnixConfig`.

# Version 0.7.1 - 2023-11-26
- [change][patch] Remove dependency on `byteorder` crate.

Expand Down
41 changes: 11 additions & 30 deletions src/message.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::Error;
use crate::error::private::InnerError;
use crate::transport::Endian;

/// The encoded length of a message header.
///
Expand Down Expand Up @@ -220,16 +221,16 @@ impl MessageHeader {
}
}

/// Decode a message header from a byte slice.
/// Decode a message header from a byte slice using the given endianness for the header fields.
///
/// The byte slice should NOT contain the message size.
///
/// # Panic
/// This function panics if the buffer does not contain a full header.
pub fn decode(buffer: &[u8]) -> Result<Self, Error> {
let message_type = read_u32_le(&buffer[0..]);
let request_id = read_u32_le(&buffer[4..]);
let service_id = read_i32_le(&buffer[8..]);
pub fn decode(buffer: &[u8], endian: Endian) -> Result<Self, Error> {
let message_type = endian.read_u32(&buffer[0..]);
let request_id = endian.read_u32(&buffer[4..]);
let service_id = endian.read_i32(&buffer[8..]);

let message_type = MessageType::from_u32(message_type)?;
Ok(Self {
Expand All @@ -239,17 +240,17 @@ impl MessageHeader {
})
}

/// Encode a message header into a byte slice.
/// Encode a message header into a byte slice using the given endianness for the header fields.
///
/// This will NOT add a message size (which would be impossible even if we wanted to).
///
/// # Panic
/// This function panics if the buffer is not large enough to hold a full header.
pub fn encode(&self, buffer: &mut [u8]) {
pub fn encode(&self, buffer: &mut [u8], endian: Endian) {
assert!(buffer.len() >= 12);
write_u32_le(&mut buffer[0..], self.message_type as u32);
write_u32_le(&mut buffer[4..], self.request_id);
write_i32_le(&mut buffer[8..], self.service_id);
endian.write_u32(&mut buffer[0..], self.message_type as u32);
endian.write_u32(&mut buffer[4..], self.request_id);
endian.write_i32(&mut buffer[8..], self.service_id);
}
}

Expand All @@ -260,23 +261,3 @@ impl<Body> std::fmt::Debug for Message<Body> {
.finish_non_exhaustive()
}
}

/// Read a [`u32`] from a buffer in little endian format.
fn read_u32_le(buffer: &[u8]) -> u32 {
u32::from_le_bytes(buffer[0..4].try_into().unwrap())
}

/// Read a [`i32`] from a buffer in little endian format.
fn read_i32_le(buffer: &[u8]) -> i32 {
i32::from_le_bytes(buffer[0..4].try_into().unwrap())
}

/// Write a [`i32`] to a buffer in little endian format.
fn write_i32_le(buffer: &mut [u8], value: i32) {
buffer[0..4].copy_from_slice(&value.to_le_bytes());
}

/// Write a [`u32`] to a buffer in little endian format.
fn write_u32_le(buffer: &mut [u8], value: u32) {
buffer[0..4].copy_from_slice(&value.to_le_bytes());
}
176 changes: 176 additions & 0 deletions src/transport/endian.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/// The endianness to use for encoding header fields.
///
/// The encoding and serialization of message bodies is up to the application code,
/// and it not affected by this configuration parameter.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Endian {
/// Encode header fields in little endian.
LittleEndian,

/// Encode header fields in big endian.
BigEndian,

/// Encode header fields in the native endianness of the platform.
///
/// NOTE: You should only use this when you know for sure that the other side of the connection
/// is on the same platform, such as when using a Unix socket.
/// Otherwise, both sides may select native endianness and end up using a different endianness.
NativeEndian,
}

impl Endian {
/// Read a [`u32`] from a buffer in the correct endianness.
pub(crate) fn read_u32(self, buffer: &[u8]) -> u32 {
let buffer = buffer[0..4].try_into().unwrap();
match self {
Self::LittleEndian => u32::from_le_bytes(buffer),
Self::BigEndian => u32::from_be_bytes(buffer),
Self::NativeEndian => u32::from_ne_bytes(buffer),
}
}

/// Write a [`u32`] to a buffer in the correct endianness.
pub(crate) fn write_u32(self, buffer: &mut [u8], value: u32) {
let bytes = match self {
Self::LittleEndian => value.to_le_bytes(),
Self::BigEndian => value.to_be_bytes(),
Self::NativeEndian => value.to_ne_bytes(),
};
buffer[0..4].copy_from_slice(&bytes);
}

/// Read a [`i32`] from a buffer in the correct endianness.
pub(crate) fn read_i32(self, buffer: &[u8]) -> i32 {
let buffer = buffer[0..4].try_into().unwrap();
match self {
Self::LittleEndian => i32::from_le_bytes(buffer),
Self::BigEndian => i32::from_be_bytes(buffer),
Self::NativeEndian => i32::from_ne_bytes(buffer),
}
}

/// Write a [`i32`] to a buffer in the correct endianness.
pub(crate) fn write_i32(self, buffer: &mut [u8], value: i32) {
let bytes = match self {
Self::LittleEndian => value.to_le_bytes(),
Self::BigEndian => value.to_be_bytes(),
Self::NativeEndian => value.to_ne_bytes(),
};
buffer[0..4].copy_from_slice(&bytes);
}
}

#[cfg(test)]
mod test {
use super::Endian;
use assert2::assert;

#[test]
fn write_u32_litte_endian_works() {
let mut buffer = [0u8; 4];
Endian::LittleEndian.write_u32(&mut buffer, 0x01020304);
assert!(buffer == [0x04, 0x03, 0x02, 0x01]);
}

#[test]
fn write_u32_big_endian_works() {
let mut buffer = [0u8; 4];
Endian::BigEndian.write_u32(&mut buffer, 0x01020304);
assert!(buffer == [0x01, 0x02, 0x03, 0x04]);
}

#[test]
fn write_u32_native_endian_works() {
let mut buffer = [0u8; 4];
Endian::LittleEndian.write_u32(&mut buffer, 0x01020304);
#[cfg(target_endian = "little")]
assert!(buffer == [0x04, 0x03, 0x02, 0x01]);
#[cfg(target_endian = "big")]
assert!(buffer == [0x01, 0x02, 0x03, 0x04]);
}

#[test]
fn read_u32_litte_endian_works() {
assert!(Endian::LittleEndian.read_u32(&[0x04, 0x03, 0x02, 0x01]) == 0x01020304);
}

#[test]
fn read_u32_big_endian_works() {
assert!(Endian::BigEndian.read_u32(&[0x01, 0x02, 0x03, 0x04]) == 0x01020304);
}

#[test]
fn read_u32_native_endian_works() {
#[cfg(target_endian = "little")]
assert!(Endian::NativeEndian.read_u32(&[0x04, 0x03, 0x02, 0x01]) == 0x01020304);
#[cfg(target_endian = "big")]
assert!(Endian::NativeEndian.read_u32(&[0x01, 0x02, 0x03, 0x04]) == 0x01020304);
}

#[test]
fn write_i32_litte_endian_works() {
let mut buffer = [0u8; 4];
Endian::LittleEndian.write_i32(&mut buffer, 0x01020304);
assert!(buffer == [0x04, 0x03, 0x02, 0x01]);

// 0x80000000 - 0x7efdfcfd = 0x01020305
Endian::LittleEndian.write_i32(&mut buffer, -0x7efdfcfb);
assert!(buffer == [0x05, 0x03, 0x02, 0x81]);
}

#[test]
fn write_i32_big_endian_works() {
let mut buffer = [0u8; 4];
Endian::BigEndian.write_i32(&mut buffer, 0x01020304);
assert!(buffer == [0x01, 0x02, 0x03, 0x04]);

// 0x80000000 - 0x7efdfcfd = 0x01020305
Endian::BigEndian.write_i32(&mut buffer, -0x7efdfcfb);
assert!(buffer == [0x81, 0x02, 0x03, 0x05]);
}

#[test]
fn write_i32_native_endian_works() {
let mut buffer = [0u8; 4];
Endian::NativeEndian.write_i32(&mut buffer, 0x01020304);
#[cfg(target_endian = "little")]
assert!(buffer == [0x04, 0x03, 0x02, 0x01]);
#[cfg(target_endian = "big")]
assert!(buffer == [0x01, 0x02, 0x03, 0x04]);

// 0x80000000 - 0x7efdfcfd = 0x01020305
Endian::NativeEndian.write_i32(&mut buffer, -0x7efdfcfb);
#[cfg(target_endian = "little")]
assert!(buffer == [0x05, 0x03, 0x02, 0x81]);
#[cfg(target_endian = "big")]
assert!(buffer == [0x81, 0x02, 0x03, 0x05]);
}

#[test]
fn read_i32_litte_endian_works() {
assert!(Endian::LittleEndian.read_i32(&[0x04, 0x03, 0x02, 0x01]) == 0x01020304);
// 0x80000000 - 0x7efdfcfd = 0x01020305
assert!(Endian::LittleEndian.read_i32(&[0x05, 0x03, 0x02, 0x81]) == -0x7efdfcfb);
}

#[test]
fn read_i32_big_endian_works() {
assert!(Endian::BigEndian.read_i32(&[0x01, 0x02, 0x03, 0x04]) == 0x01020304);
// 0x80000000 - 0x7efdfcfd = 0x01020305
assert!(Endian::BigEndian.read_i32(&[0x81, 0x02, 0x03, 0x05]) == -0x7efdfcfb);
}

#[test]
fn read_i32_native_endian_works() {
#[cfg(target_endian = "little")]
assert!(Endian::NativeEndian.read_i32(&[0x04, 0x03, 0x02, 0x01]) == 0x01020304);
// 0x80000000 - 0x7efdfcfd = 0x01020305
#[cfg(target_endian = "little")]
assert!(Endian::NativeEndian.read_i32(&[0x05, 0x03, 0x02, 0x81]) == -0x7efdfcfb);

#[cfg(target_endian = "big")]
assert!(Endian::NativeEndian.read_i32(&[0x01, 0x02, 0x03, 0x04]) == 0x01020304);
#[cfg(target_endian = "big")]
assert!(Endian::NativeEndian.read_i32(&[0x81, 0x02, 0x03, 0x05]) == -0x7efdfcfb);
}
}
3 changes: 3 additions & 0 deletions src/transport/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use std::task::{Context, Poll};

use crate::{Error, Message, MessageHeader};

mod endian;
pub use endian::Endian;

pub(crate) mod stream;
pub use stream::StreamTransport;

Expand Down
10 changes: 10 additions & 0 deletions src/transport/stream/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::transport::Endian;

/// Configuration for a byte-stream transport.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct StreamConfig {
/// The maximum body size for incoming messages.
///
Expand All @@ -13,13 +16,20 @@ pub struct StreamConfig {
/// the message is discarded and an error is returned.
/// Stream sockets remain usable since the message header will not be sent either.
pub max_body_len_write: u32,

/// The endianness to use when encoding/decoding header fields.
///
/// The encoding and serialization of message bodies is up to the application code,
/// and it not affected by this configuration parameter.
pub endian: Endian,
}

impl Default for StreamConfig {
fn default() -> Self {
Self {
max_body_len_read: 8 * 1024,
max_body_len_write: 8 * 1024,
endian: Endian::LittleEndian,
}
}
}
8 changes: 4 additions & 4 deletions src/transport/stream/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ mod impl_unix_stream {

fn split(&mut self) -> (StreamReadHalf<tokio::net::unix::ReadHalf>, StreamWriteHalf<tokio::net::unix::WriteHalf>) {
let (read_half, write_half) = self.stream.split();
let read_half = StreamReadHalf::new(read_half, self.config.max_body_len_read);
let write_half = StreamWriteHalf::new(write_half, self.config.max_body_len_write);
let read_half = StreamReadHalf::new(read_half, self.config.max_body_len_read, self.config.endian);
let write_half = StreamWriteHalf::new(write_half, self.config.max_body_len_write, self.config.endian);
(read_half, write_half)
}

Expand Down Expand Up @@ -155,8 +155,8 @@ mod impl_tcp {

fn split(&mut self) -> (StreamReadHalf<tokio::net::tcp::ReadHalf>, StreamWriteHalf<tokio::net::tcp::WriteHalf>) {
let (read_half, write_half) = self.stream.split();
let read_half = StreamReadHalf::new(read_half, self.config.max_body_len_read);
let write_half = StreamWriteHalf::new(write_half, self.config.max_body_len_write);
let read_half = StreamReadHalf::new(read_half, self.config.max_body_len_read, self.config.endian);
let write_half = StreamWriteHalf::new(write_half, self.config.max_body_len_write, self.config.endian);
(read_half, write_half)
}

Expand Down
Loading

0 comments on commit 9691a6b

Please sign in to comment.