From 95332b131e45e3e50dfccac16d14eb1555b74cab Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Sun, 9 Oct 2022 18:24:57 +0100 Subject: [PATCH] Replaced `zutil`'s `split_array` interface with `ndsz-bytes`. --- ndsz-bytes/Cargo.toml | 13 ++++ ndsz-bytes/src/array_split.rs | 111 ++++++++++++++++++++++++++++++++ ndsz-bytes/src/byteorder_ext.rs | 82 +++++++++++++++++++++++ ndsz-bytes/src/bytes.rs | 79 +++++++++++++++++++++++ ndsz-bytes/src/bytes_io_ext.rs | 55 ++++++++++++++++ ndsz-bytes/src/lib.rs | 22 +++++++ ndsz-bytes/src/validate.rs | 33 ++++++++++ ndsz-fat/Cargo.toml | 3 + ndsz-fat/src/file_ptr.rs | 2 +- ndsz-fat/src/fnt/main_table.rs | 2 +- ndsz-generic-header/Cargo.toml | 3 + ndsz-generic-header/src/lib.rs | 2 +- ndsz-narc/Cargo.toml | 1 + ndsz-narc/src/data.rs | 2 +- ndsz-narc/src/fat_header.rs | 2 +- ndsz-narc/src/fnt_header.rs | 2 +- ndsz-narc/src/header.rs | 2 +- ndsz-nds/Cargo.toml | 3 + ndsz-nds/src/header.rs | 6 +- 19 files changed, 415 insertions(+), 10 deletions(-) create mode 100644 ndsz-bytes/Cargo.toml create mode 100644 ndsz-bytes/src/array_split.rs create mode 100644 ndsz-bytes/src/byteorder_ext.rs create mode 100644 ndsz-bytes/src/bytes.rs create mode 100644 ndsz-bytes/src/bytes_io_ext.rs create mode 100644 ndsz-bytes/src/lib.rs create mode 100644 ndsz-bytes/src/validate.rs diff --git a/ndsz-bytes/Cargo.toml b/ndsz-bytes/Cargo.toml new file mode 100644 index 0000000..16036c8 --- /dev/null +++ b/ndsz-bytes/Cargo.toml @@ -0,0 +1,13 @@ +[package] +edition = "2018" +name = "ndsz-bytes" +version = "0.1.0" + +[dependencies] + +# Bytes +arrayref = "0.3.6" +byteorder = "1.4.2" + +# Error handling +thiserror = "1.0.23" diff --git a/ndsz-bytes/src/array_split.rs b/ndsz-bytes/src/array_split.rs new file mode 100644 index 0000000..5fb9c99 --- /dev/null +++ b/ndsz-bytes/src/array_split.rs @@ -0,0 +1,111 @@ +//! Array splitting + +// TODO: Add back a `PhantomData` field for when splitting nothing once +// rust-analyzer doesn't crap itself with it + +/// Splits a byte array reference into several smaller byte arrays references, +/// or even single byte references. +pub macro array_split + ( + $arr:expr, + $( + $name:ident : + + $( [$arr_size:expr] )? + $( $val_size:literal )? + + ),* $(,)? + ) {{ + // Struct holding all fields + #[derive(Clone, Copy, Debug)] + struct Fields<'a, T> { + $( + pub $name: + + $( &'a [T; $arr_size], )? + $( &'a T, #[cfg(invalid)] __field: [u8; $val_size], )? + )* + } + + // Get everything from `array_refs` + #[allow( + clippy::used_underscore_binding, + clippy::ptr_offset_with_cast, + clippy::indexing_slicing, + )] + let ( + $( + $name + ),* + ) = $crate::arrayref::array_refs!( + $arr, + $( + $( $arr_size )? + $( $val_size )? + ),* + ); + + // And return the fields + Fields { + $( + $name + $( : &( $name[$val_size - $val_size] ) )? + , + )* + } + }} + +/// Splits a byte array mutable reference into several smaller byte arrays references, +/// or even single byte references. +#[allow(clippy::module_name_repetitions)] // `_mut` version should be in the same module +pub macro array_split_mut( + $arr:expr, + $( + $name:ident : + + $( [$arr_size:expr] )? + $( $val_size:literal )? + + ),* $(,)? + ) {{ + // Struct holding all fields + #[derive(Debug)] + struct Fields<'a, T> { + $( + pub $name: + + $( &'a mut [T; $arr_size], )? + // Note: This `cfg` is simply done so that `__field` never appears. + // The `__field` serves to identify when this part should be written. + $( &'a mut T, #[cfg(invalid)] __field: [u8; $val_size], )? + )* + } + + // Get everything from `mut_array_refs` + #[allow( + clippy::used_underscore_binding, + clippy::ptr_offset_with_cast, + clippy::indexing_slicing, + )] + let ( + $( + $name + ),* + ) = $crate::arrayref::mut_array_refs!( + $arr, + $( + $( $arr_size )? + $( $val_size )? + ),* + ); + + // And return the fields + Fields { + $( + $name + // Note: This serves to turn a `&mut [u8; 1]` into a `&mut u8`. + $( : &mut ( $name[$val_size - $val_size] ) )? + , + )* + } + }} diff --git a/ndsz-bytes/src/byteorder_ext.rs b/ndsz-bytes/src/byteorder_ext.rs new file mode 100644 index 0000000..5bdbd25 --- /dev/null +++ b/ndsz-bytes/src/byteorder_ext.rs @@ -0,0 +1,82 @@ +//! [`ByteOrder`] extension trait + +// Imports +use {crate::ByteArray, byteorder::ByteOrder}; + +/// Helper trait for [`ByteOrder`] to use a generic type +pub trait ByteOrderExt: Sized { + /// Array type + type ByteArray: ByteArray; + + /// Reads this type + fn read(bytes: &Self::ByteArray) -> Self; + + /// Writes this type + fn write(&self, bytes: &mut Self::ByteArray); +} + +impl ByteOrderExt for [u8; N] { + type ByteArray = [u8; N]; + + fn read(bytes: &Self::ByteArray) -> Self { + *bytes + } + + fn write(&self, bytes: &mut Self::ByteArray) { + *bytes = *self; + } +} + +#[allow(clippy::use_self)] // We want the byte buffer to be `[u8; _]` +impl ByteOrderExt for u8 { + type ByteArray = [u8; 1]; + + fn read(bytes: &Self::ByteArray) -> Self { + bytes[0] + } + + fn write(&self, bytes: &mut Self::ByteArray) { + bytes[0] = *self; + } +} + +#[allow(clippy::as_conversions, clippy::cast_possible_wrap, clippy::cast_sign_loss)] // We want to explicitly convert it from bytes +impl ByteOrderExt for i8 { + type ByteArray = [u8; 1]; + + fn read(bytes: &Self::ByteArray) -> Self { + bytes[0] as Self + } + + fn write(&self, bytes: &mut Self::ByteArray) { + bytes[0] = *self as u8; + } +} + +/// Implements [`ByteOrderExt`] for `$T` with size `$SIZE` and methods to read/write `$read_func`/`$write_func` +macro_rules! impl_read_bytes { + ($($T:ty, $SIZE:literal, $read_func:ident, $write_func:ident),* $(,)?) => { + $( + impl ByteOrderExt for $T { + type ByteArray = [u8; $SIZE]; + + fn read(bytes: &Self::ByteArray) -> Self { + B::$read_func(bytes) + } + + fn write(&self, bytes: &mut Self::ByteArray) { + B::$write_func(bytes, *self); + } + } + )* + }; +} + +impl_read_bytes! { + u16, 2, read_u16, write_u16, + u32, 4, read_u32, write_u32, + u64, 8, read_u64, write_u64, + i16, 2, read_i16, write_i16, + i32, 4, read_i32, write_i32, + i64, 8, read_i64, write_i64, +} diff --git a/ndsz-bytes/src/bytes.rs b/ndsz-bytes/src/bytes.rs new file mode 100644 index 0000000..f653600 --- /dev/null +++ b/ndsz-bytes/src/bytes.rs @@ -0,0 +1,79 @@ +//! `Bytes` trait. + +// Imports +use std::error::Error; + +/// Conversion from and to bytes +pub trait Bytes +where + Self: Sized, +{ + /// The type of array required by this structure + type ByteArray: ByteArray; + + /// The error type used for the operation + type DeserializeError: Error; + + /// The error type used for the operation + type SerializeError: Error; + + /// Deserializes this from `bytes` + fn deserialize_bytes(bytes: &Self::ByteArray) -> Result; + + /// Serializes this to `bytes` + fn serialize_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::SerializeError>; + + /// Serializes this to bytes and returns them. + fn to_bytes(&self) -> Result { + let mut bytes = Self::ByteArray::zeros(); + self.serialize_bytes(&mut bytes)?; + Ok(bytes) + } +} + +/// A trait for restricting `Bytes::ByteArray` +pub trait ByteArray { + /// Array size + const SIZE: usize; + + /// Returns this array as a slice + fn as_slice(&self) -> &[u8]; + + /// Returns this array as a slice mutably + fn as_slice_mut(&mut self) -> &mut [u8]; + + /// Creates a new array filled with `0`s + fn zeros() -> Self; +} + +impl ByteArray for [u8; N] { + const SIZE: usize = N; + + fn as_slice(&self) -> &[u8] { + self + } + + fn as_slice_mut(&mut self) -> &mut [u8] { + self + } + + fn zeros() -> Self { + [0; N] + } +} + +impl ByteArray for u8 { + const SIZE: usize = 1; + + fn as_slice(&self) -> &[u8] { + std::slice::from_ref(self) + } + + fn as_slice_mut(&mut self) -> &mut [u8] { + std::slice::from_mut(self) + } + + fn zeros() -> Self { + 0 + } +} diff --git a/ndsz-bytes/src/bytes_io_ext.rs b/ndsz-bytes/src/bytes_io_ext.rs new file mode 100644 index 0000000..4464b16 --- /dev/null +++ b/ndsz-bytes/src/bytes_io_ext.rs @@ -0,0 +1,55 @@ +//! Bytes io extensions + +// Imports +use { + crate::{ByteArray, Bytes}, + std::{error, fmt, io}, +}; + +/// Bytes read extension trait +pub trait BytesReadExt: io::Read { + /// Deserializes `B` from this stream + fn read_deserialize(&mut self) -> Result> { + let mut bytes = B::ByteArray::zeros(); + self.read_exact(bytes.as_slice_mut()) + .map_err(ReadDeserializeError::Read)?; + B::deserialize_bytes(&bytes).map_err(ReadDeserializeError::Parse) + } +} + +impl BytesReadExt for R {} + +/// Bytes write extension trait +pub trait BytesWriteExt: io::Write { + /// Serializes `B` to this stream + fn write_serialize(&mut self, value: &B) -> Result<(), WriteSerializeError> { + let bytes = value.to_bytes().map_err(WriteSerializeError::Serialize)?; + self.write_all(bytes.as_slice()).map_err(WriteSerializeError::Write) + } +} + +impl BytesWriteExt for W {} + +/// Read bytes error +#[derive(Debug, thiserror::Error)] +pub enum ReadDeserializeError { + /// Unable to read bytes + #[error("Unable to read bytes")] + Read(#[source] io::Error), + + /// Unable to parse bytes + #[error("Unable to parse bytes")] + Parse(#[source] E), +} + +/// Write bytes error +#[derive(Debug, thiserror::Error)] +pub enum WriteSerializeError { + /// Unable to serialize value + #[error("Unable to serialize value")] + Serialize(#[source] E), + + /// Unable to write bytes + #[error("Unable to write bytes")] + Write(#[source] io::Error), +} diff --git a/ndsz-bytes/src/lib.rs b/ndsz-bytes/src/lib.rs new file mode 100644 index 0000000..4cdbbe4 --- /dev/null +++ b/ndsz-bytes/src/lib.rs @@ -0,0 +1,22 @@ +//! Helper crate for working with raws bytes. + +// Features +#![feature(decl_macro)] + +// Modules +mod array_split; +mod byteorder_ext; +mod bytes; +pub mod bytes_io_ext; +pub mod validate; + +// Exports +pub use self::{ + array_split::{array_split, array_split_mut}, + byteorder_ext::ByteOrderExt, + bytes::{ByteArray, Bytes}, + bytes_io_ext::{BytesReadExt, BytesWriteExt}, + validate::{Validate, ValidateVisitor}, +}; +#[doc(hidden)] +pub use ::arrayref; // Export `arrayref` to use in macros diff --git a/ndsz-bytes/src/validate.rs b/ndsz-bytes/src/validate.rs new file mode 100644 index 0000000..d075fc5 --- /dev/null +++ b/ndsz-bytes/src/validate.rs @@ -0,0 +1,33 @@ +//! Validation interface + +/// Structures that are validatable before being written to bytes. +/// +/// This works in tandem with the [`Bytes`](crate::Bytes) interface to allow +/// applications which take user input to validate input before serializing it. +/// +/// Although this information exists by calling [`Bytes::serialize_bytes`](crate::Bytes::serialize_bytes), +/// this interface provides two main advantages: +/// +/// 1. It is faster than serializing the data, as it doesn't need to write the raw bytes and +/// can focus on simply parsing possible errors. +/// 2. It provides warnings alongside the errors. These are also provided via `log::warn`, but +/// these cannot be sent to the user easily. +pub trait Validate<'a> { + /// Error type for this validation + type Error: 'a; + + /// Warning type for this validation + type Warning: 'a; + + /// Validates this type with the visitor `visitor` + fn validate>(&'a self, visitor: V); +} + +/// A validate visitor, used to collect errors and warnings +pub trait ValidateVisitor<'a, T: ?Sized + Validate<'a>> { + /// Visits a warning + fn visit_warning(&mut self, warning: T::Warning); + + /// Visits an error + fn visit_error(&mut self, error: T::Error); +} diff --git a/ndsz-fat/Cargo.toml b/ndsz-fat/Cargo.toml index 3c809e8..1d7e67b 100644 --- a/ndsz-fat/Cargo.toml +++ b/ndsz-fat/Cargo.toml @@ -7,6 +7,9 @@ version = "0.0.0" [dependencies] +# Ndsz +ndsz-bytes = {path = "../ndsz-bytes"} + # Bytes byteorder = "1.4.3" diff --git a/ndsz-fat/src/file_ptr.rs b/ndsz-fat/src/file_ptr.rs index 9f4b255..af31a81 100644 --- a/ndsz-fat/src/file_ptr.rs +++ b/ndsz-fat/src/file_ptr.rs @@ -16,7 +16,7 @@ pub struct FilePtr { impl FilePtr { /// Parses a header data from bytes pub fn from_bytes(bytes: &[u8; 0x8]) -> Self { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, start_address: [0x4], end_address: [0x4], ); diff --git a/ndsz-fat/src/fnt/main_table.rs b/ndsz-fat/src/fnt/main_table.rs index 46ebf0c..acbfe9f 100644 --- a/ndsz-fat/src/fnt/main_table.rs +++ b/ndsz-fat/src/fnt/main_table.rs @@ -69,7 +69,7 @@ pub struct MainTableEntry { impl MainTableEntry { /// Parses an entry from bytes pub fn from_bytes(bytes: &[u8; 0x8]) -> Self { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, sub_table_offset: [0x4], first_file_id : [0x2], parent_id : [0x2], diff --git a/ndsz-generic-header/Cargo.toml b/ndsz-generic-header/Cargo.toml index 76785ac..ba2bf5f 100644 --- a/ndsz-generic-header/Cargo.toml +++ b/ndsz-generic-header/Cargo.toml @@ -7,6 +7,9 @@ version = "0.0.0" [dependencies] +# Ndsz +ndsz-bytes = {path = "../ndsz-bytes"} + # Bytes byteorder = "1.4.3" diff --git a/ndsz-generic-header/src/lib.rs b/ndsz-generic-header/src/lib.rs index c07503e..736328e 100644 --- a/ndsz-generic-header/src/lib.rs +++ b/ndsz-generic-header/src/lib.rs @@ -28,7 +28,7 @@ pub struct Header { impl Header { /// Parses a header from bytes pub fn from_bytes(bytes: &[u8; 0x10]) -> Result { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, magic : [0x4], constant : [0x4], section_size : [0x4], diff --git a/ndsz-narc/Cargo.toml b/ndsz-narc/Cargo.toml index 6d813ab..27a180e 100644 --- a/ndsz-narc/Cargo.toml +++ b/ndsz-narc/Cargo.toml @@ -8,6 +8,7 @@ version = "0.0.0" [dependencies] # Ndsz +ndsz-bytes = {path = "../ndsz-bytes"} ndsz-fat = {path = "../ndsz-fat"} # Bytes diff --git a/ndsz-narc/src/data.rs b/ndsz-narc/src/data.rs index 7139ee1..b703a08 100644 --- a/ndsz-narc/src/data.rs +++ b/ndsz-narc/src/data.rs @@ -62,7 +62,7 @@ impl Header { /// Parses a header data from bytes pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Option { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, chunk_name: [0x4], chunk_size: [0x4], ); diff --git a/ndsz-narc/src/fat_header.rs b/ndsz-narc/src/fat_header.rs index bf8e480..beb501d 100644 --- a/ndsz-narc/src/fat_header.rs +++ b/ndsz-narc/src/fat_header.rs @@ -22,7 +22,7 @@ impl FatHeader { /// Parses a header data from bytes pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Option { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, chunk_name: [0x4], chunk_size: [0x4], files_len : [0x2], diff --git a/ndsz-narc/src/fnt_header.rs b/ndsz-narc/src/fnt_header.rs index 5857493..e8aaa26 100644 --- a/ndsz-narc/src/fnt_header.rs +++ b/ndsz-narc/src/fnt_header.rs @@ -16,7 +16,7 @@ impl FntHeader { /// Parses a header data from bytes pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Option { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, chunk_name: [0x4], chunk_size: [0x4], ); diff --git a/ndsz-narc/src/header.rs b/ndsz-narc/src/header.rs index 164ebab..0d059ee 100644 --- a/ndsz-narc/src/header.rs +++ b/ndsz-narc/src/header.rs @@ -19,7 +19,7 @@ pub struct Header { impl Header { /// Parses a header data from bytes pub fn from_bytes(bytes: &[u8; 0x10]) -> Result { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, chunk_name: [0x4], byte_order: [0x2], version : [0x2], diff --git a/ndsz-nds/Cargo.toml b/ndsz-nds/Cargo.toml index df8047f..682e1b1 100644 --- a/ndsz-nds/Cargo.toml +++ b/ndsz-nds/Cargo.toml @@ -7,6 +7,9 @@ version = "0.0.0" [dependencies] +# Ndsz +ndsz-bytes = {path = "../ndsz-bytes"} + # Bytes byteorder = "1.4.3" diff --git a/ndsz-nds/src/header.rs b/ndsz-nds/src/header.rs index 1a8e881..f5494b9 100644 --- a/ndsz-nds/src/header.rs +++ b/ndsz-nds/src/header.rs @@ -114,7 +114,7 @@ pub struct Header { impl Header { /// Parses a header data from bytes pub fn from_bytes(bytes: &[u8; 0x180]) -> Result { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, game_title : [0xc], // 0x0 game_code : [0x4], // 0xc maker_code : [0x2], // 0x10 @@ -199,7 +199,7 @@ pub struct TableLoadData { impl TableLoadData { /// Parses a table load data from bytes pub fn from_bytes(bytes: &[u8; 8]) -> Self { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, offset: [0x4], length: [0x4], ); @@ -230,7 +230,7 @@ pub struct ArmLoadData { impl ArmLoadData { /// Parses load data from bytes pub fn from_bytes(bytes: &[u8; 16]) -> Self { - let bytes = zutil::array_split!(bytes, + let bytes = ndsz_bytes::array_split!(bytes, offset : [0x4], entry_address: [0x4], load_address : [0x4],