diff --git a/CHANGELOG b/CHANGELOG index 0efe0c2..31006bc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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. diff --git a/src/message.rs b/src/message.rs index 26666e5..e8e8f9e 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,5 +1,6 @@ use crate::Error; use crate::error::private::InnerError; +use crate::transport::Endian; /// The encoded length of a message header. /// @@ -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 { - 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 { + 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 { @@ -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); } } @@ -260,23 +261,3 @@ impl std::fmt::Debug for Message { .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()); -} diff --git a/src/transport/endian.rs b/src/transport/endian.rs new file mode 100644 index 0000000..b73dea8 --- /dev/null +++ b/src/transport/endian.rs @@ -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); + } +} diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 3b96223..7552530 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -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; diff --git a/src/transport/stream/config.rs b/src/transport/stream/config.rs index d9f7bfe..228363c 100644 --- a/src/transport/stream/config.rs +++ b/src/transport/stream/config.rs @@ -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. /// @@ -13,6 +16,12 @@ 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 { @@ -20,6 +29,7 @@ impl Default for StreamConfig { Self { max_body_len_read: 8 * 1024, max_body_len_write: 8 * 1024, + endian: Endian::LittleEndian, } } } diff --git a/src/transport/stream/mod.rs b/src/transport/stream/mod.rs index 92b6fe3..cdda80a 100644 --- a/src/transport/stream/mod.rs +++ b/src/transport/stream/mod.rs @@ -53,8 +53,8 @@ mod impl_unix_stream { fn split(&mut self) -> (StreamReadHalf, StreamWriteHalf) { 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) } @@ -155,8 +155,8 @@ mod impl_tcp { fn split(&mut self) -> (StreamReadHalf, StreamWriteHalf) { 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) } diff --git a/src/transport/stream/transport.rs b/src/transport/stream/transport.rs index ce45f21..722a8f4 100644 --- a/src/transport/stream/transport.rs +++ b/src/transport/stream/transport.rs @@ -5,7 +5,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; use super::{StreamBody, StreamConfig}; use crate::error::private::check_payload_too_large; -use crate::transport::TransportError; +use crate::transport::{TransportError, Endian}; use crate::{Message, MessageHeader}; /// Length of a message frame and header. @@ -30,6 +30,9 @@ pub struct StreamReadHalf { /// The maximum body length to accept when reading messages. pub(super) max_body_len: u32, + /// The endianness to use for decoding header fields. + pub(super) endian: Endian, + /// The number of bytes read for the current message. pub(super) bytes_read: usize, @@ -52,6 +55,9 @@ pub struct StreamWriteHalf { /// The maximum body length to enforce for messages. pub(super) max_body_len: u32, + /// The endianness to use for encoding header fields. + pub(super) endian: Endian, + /// The number of bytes written for the current message. pub(super) bytes_written: usize, @@ -91,10 +97,11 @@ where impl StreamReadHalf { #[allow(dead_code)] // Not used when transports are disabled. - pub(super) fn new(stream: ReadStream, max_body_len: u32) -> Self { + pub(super) fn new(stream: ReadStream, max_body_len: u32, endian: Endian) -> Self { Self { stream, max_body_len, + endian, header_buffer: [0u8; FRAMED_HEADER_LEN], bytes_read: 0, parsed_header: MessageHeader::request(0, 0), @@ -117,10 +124,11 @@ impl StreamReadHalf { impl StreamWriteHalf { #[allow(dead_code)] // Not used when transports are disabled. - pub(super) fn new(stream: WriteStream, max_body_len: u32) -> Self { + pub(super) fn new(stream: WriteStream, max_body_len: u32, endian: Endian) -> Self { Self { stream, max_body_len, + endian, header_buffer: None, bytes_written: 0, } @@ -171,8 +179,8 @@ where // Check if we have the whole frame + header. if this.bytes_read == FRAMED_HEADER_LEN { // Parse frame and header. - let length = read_u32_le(&this.header_buffer[0..]); - this.parsed_header = MessageHeader::decode(&this.header_buffer[4..]) + let length = this.endian.read_u32(&this.header_buffer[0..]); + this.parsed_header = MessageHeader::decode(&this.header_buffer[4..], this.endian) .map_err(TransportError::new_fatal)?; // Check body length and create body buffer. @@ -218,8 +226,8 @@ where // Encode the header if we haven't done that yet. let header_buffer = this.header_buffer.get_or_insert_with(|| { let mut buffer = [0u8; FRAMED_HEADER_LEN]; - write_u32_le(&mut buffer[0..], body.len() as u32 + crate::HEADER_LEN); - header.encode(&mut buffer[4..]); + this.endian.write_u32(&mut buffer[0..], body.len() as u32 + crate::HEADER_LEN); + header.encode(&mut buffer[4..], this.endian); buffer }); @@ -241,13 +249,3 @@ where Poll::Ready(Ok(())) } } - -/// 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()) -} - -/// 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()); -} diff --git a/src/transport/unix/config.rs b/src/transport/unix/config.rs index a8f20cb..010f04a 100644 --- a/src/transport/unix/config.rs +++ b/src/transport/unix/config.rs @@ -1,5 +1,8 @@ +use crate::transport::Endian; + /// Configuration for Unix datagram transports. #[derive(Debug, Clone)] +#[non_exhaustive] pub struct UnixConfig { /// The maximum body size for incoming messages. /// @@ -22,6 +25,12 @@ pub struct UnixConfig { /// The maximum number of attached file descriptors for sending messages. pub max_fds_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 UnixConfig { @@ -31,6 +40,7 @@ impl Default for UnixConfig { max_body_len_write: 4 * 1024, max_fds_read: 10, max_fds_write: 10, + endian: Endian::NativeEndian, } } } diff --git a/src/transport/unix/mod.rs b/src/transport/unix/mod.rs index 418d9eb..08501f2 100644 --- a/src/transport/unix/mod.rs +++ b/src/transport/unix/mod.rs @@ -53,8 +53,8 @@ mod impl_unix_seqpacket { fn split(&mut self) -> (UnixReadHalf<&tokio_seqpacket::UnixSeqpacket>, UnixWriteHalf<&tokio_seqpacket::UnixSeqpacket>) { let (read_half, write_half) = (&self.socket, &self.socket); - let read_half = UnixReadHalf::new(read_half, self.config.max_body_len_read, self.config.max_fds_read); - let write_half = UnixWriteHalf::new(write_half, self.config.max_body_len_write, self.config.max_fds_write); + let read_half = UnixReadHalf::new(read_half, self.config.max_body_len_read, self.config.max_fds_read, self.config.endian); + let write_half = UnixWriteHalf::new(write_half, self.config.max_body_len_write, self.config.max_fds_write, self.config.endian); (read_half, write_half) } diff --git a/src/transport/unix/transport.rs b/src/transport/unix/transport.rs index 7022d9e..f5fa41f 100644 --- a/src/transport/unix/transport.rs +++ b/src/transport/unix/transport.rs @@ -1,4 +1,5 @@ use crate::UnixConfig; +use crate::transport::Endian; /// Transport layer for Unix datagram/seqpacket sockets. #[allow(dead_code)] // Fields are not used when transports are disabled. @@ -22,6 +23,9 @@ pub struct UnixReadHalf { /// The maximum number of file descriptors to accept when reading messages. pub(super) max_fds: u32, + /// The endianness to use for decoding header fields. + pub(super) endian: Endian, + /// Buffer for reading the message body. pub(super) body_buffer: Vec, } @@ -37,6 +41,9 @@ pub struct UnixWriteHalf { /// The maximum number of file descriptors to accept when writing messages. pub(super) max_fds: u32, + + /// The endianness to use for encoding header fields. + pub(super) endian: Endian, } impl UnixTransport @@ -71,11 +78,12 @@ where impl UnixReadHalf { #[allow(dead_code)] // Not used when transports are disabled. - pub(super) fn new(socket: SocketReadHalf, max_body_len: u32, max_fds: u32) -> Self { + pub(super) fn new(socket: SocketReadHalf, max_body_len: u32, max_fds: u32, endian: Endian) -> Self { Self { socket, max_body_len, max_fds, + endian, body_buffer: Vec::new(), } } @@ -95,11 +103,12 @@ impl UnixReadHalf { impl UnixWriteHalf { #[allow(dead_code)] // Not used when transports are disabled. - pub(super) fn new(socket: SocketWriteHalf, max_body_len: u32, max_fds: u32) -> Self { + pub(super) fn new(socket: SocketWriteHalf, max_body_len: u32, max_fds: u32, endian: Endian) -> Self { Self { socket, max_body_len, max_fds, + endian, } } @@ -170,7 +179,7 @@ mod implementation { .map_err(TransportError::new_fatal)?; // Parse the header. - let header = MessageHeader::decode(&header_buffer) + let header = MessageHeader::decode(&header_buffer, this.endian) .map_err(TransportError::new_fatal)?; // Resize the body buffer to the actual body size. @@ -193,7 +202,7 @@ mod implementation { // Prepare a buffer for the message header. let mut header_buffer = [0; crate::HEADER_LEN as usize]; - header.encode(&mut header_buffer); + header.encode(&mut header_buffer, this.endian); // Prepare a buffer for the ancillary data. // TODO: properly compute size of ancillary buffer.