Replaced zutil's split_array interface with ndsz-bytes.

This commit is contained in:
Filipe Rodrigues 2022-10-09 18:24:57 +01:00
parent f515cbed6f
commit 95332b131e
19 changed files with 415 additions and 10 deletions

13
ndsz-bytes/Cargo.toml Normal file
View File

@ -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"

View File

@ -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] ) )?
,
)*
}
}}

View File

@ -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<B: ByteOrder>: 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<B: ByteOrder, const N: usize> ByteOrderExt<B> 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<B: ByteOrder> ByteOrderExt<B> 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<B: ByteOrder> ByteOrderExt<B> 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<B: ByteOrder> ByteOrderExt<B> 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,
}

79
ndsz-bytes/src/bytes.rs Normal file
View File

@ -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<Self, Self::DeserializeError>;
/// 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<Self::ByteArray, Self::SerializeError> {
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<const N: usize> 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
}
}

View File

@ -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<B: Bytes>(&mut self) -> Result<B, ReadDeserializeError<B::DeserializeError>> {
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<R: io::Read> BytesReadExt for R {}
/// Bytes write extension trait
pub trait BytesWriteExt: io::Write {
/// Serializes `B` to this stream
fn write_serialize<B: Bytes>(&mut self, value: &B) -> Result<(), WriteSerializeError<B::SerializeError>> {
let bytes = value.to_bytes().map_err(WriteSerializeError::Serialize)?;
self.write_all(bytes.as_slice()).map_err(WriteSerializeError::Write)
}
}
impl<W: io::Write> BytesWriteExt for W {}
/// Read bytes error
#[derive(Debug, thiserror::Error)]
pub enum ReadDeserializeError<E: fmt::Debug + error::Error + 'static> {
/// 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<E: fmt::Debug + error::Error + 'static> {
/// 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),
}

22
ndsz-bytes/src/lib.rs Normal file
View File

@ -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

View File

@ -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<V: ValidateVisitor<'a, Self>>(&'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);
}

View File

@ -7,6 +7,9 @@ version = "0.0.0"
[dependencies]
# Ndsz
ndsz-bytes = {path = "../ndsz-bytes"}
# Bytes
byteorder = "1.4.3"

View File

@ -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],
);

View File

@ -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],

View File

@ -7,6 +7,9 @@ version = "0.0.0"
[dependencies]
# Ndsz
ndsz-bytes = {path = "../ndsz-bytes"}
# Bytes
byteorder = "1.4.3"

View File

@ -28,7 +28,7 @@ pub struct Header {
impl Header {
/// Parses a header from bytes
pub fn from_bytes(bytes: &[u8; 0x10]) -> Result<Self, FromBytesError> {
let bytes = zutil::array_split!(bytes,
let bytes = ndsz_bytes::array_split!(bytes,
magic : [0x4],
constant : [0x4],
section_size : [0x4],

View File

@ -8,6 +8,7 @@ version = "0.0.0"
[dependencies]
# Ndsz
ndsz-bytes = {path = "../ndsz-bytes"}
ndsz-fat = {path = "../ndsz-fat"}
# Bytes

View File

@ -62,7 +62,7 @@ impl Header {
/// Parses a header data from bytes
pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Option<Self> {
let bytes = zutil::array_split!(bytes,
let bytes = ndsz_bytes::array_split!(bytes,
chunk_name: [0x4],
chunk_size: [0x4],
);

View File

@ -22,7 +22,7 @@ impl FatHeader {
/// Parses a header data from bytes
pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Option<Self> {
let bytes = zutil::array_split!(bytes,
let bytes = ndsz_bytes::array_split!(bytes,
chunk_name: [0x4],
chunk_size: [0x4],
files_len : [0x2],

View File

@ -16,7 +16,7 @@ impl FntHeader {
/// Parses a header data from bytes
pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Option<Self> {
let bytes = zutil::array_split!(bytes,
let bytes = ndsz_bytes::array_split!(bytes,
chunk_name: [0x4],
chunk_size: [0x4],
);

View File

@ -19,7 +19,7 @@ pub struct Header {
impl Header {
/// Parses a header data from bytes
pub fn from_bytes(bytes: &[u8; 0x10]) -> Result<Self, FromBytesError> {
let bytes = zutil::array_split!(bytes,
let bytes = ndsz_bytes::array_split!(bytes,
chunk_name: [0x4],
byte_order: [0x2],
version : [0x2],

View File

@ -7,6 +7,9 @@ version = "0.0.0"
[dependencies]
# Ndsz
ndsz-bytes = {path = "../ndsz-bytes"}
# Bytes
byteorder = "1.4.3"

View File

@ -114,7 +114,7 @@ pub struct Header {
impl Header {
/// Parses a header data from bytes
pub fn from_bytes(bytes: &[u8; 0x180]) -> Result<Self, FromBytesError> {
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],