Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
This commit is contained in:
1
vendor/lebe/.cargo-checksum.json
vendored
Normal file
1
vendor/lebe/.cargo-checksum.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"files":{"Cargo.toml":"365b393a87b2706f0eb52139c1ce3909e47ca4e1226cf7bdcae4e4c1d0f386b5","LICENSE-BSD-3-Clause":"f1b2855ae21da69e0a02499e13f57f3be92db7abbbffe9a89f33e04e9e21ec81","README.md":"d9f3a595b28433de13f0c88ae049a73af3d88068f08dd8364d45504224651448","benches/benches.rs":"a73feca65ddde13e082121cc2268318fc72a87029f2d7106f939a4356af3e232","src/lib.rs":"72567c8ad6ae8e59d59ce2e9ffcfa8f013bb24f104b128d92a52f034f592b777","tests/tests.rs":"76b860f51bef80c4223f06998fbed540d9bcd0ddc4fa1ed0f0c0964e3d58bdcc"},"package":"03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"}
|
61
vendor/lebe/Cargo.toml
vendored
Normal file
61
vendor/lebe/Cargo.toml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "lebe"
|
||||
version = "0.5.2"
|
||||
authors = ["johannesvollmer <johannes596@t-online.de>"]
|
||||
description = "Tiny, dead simple, high performance endianness conversions with a generic API"
|
||||
documentation = "https://docs.rs/crate/lebe/"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"endianness",
|
||||
"binary",
|
||||
"io",
|
||||
"byteorder",
|
||||
"endian",
|
||||
]
|
||||
categories = [
|
||||
"encoding",
|
||||
"filesystem",
|
||||
"algorithms",
|
||||
]
|
||||
license = "BSD-3-Clause"
|
||||
repository = "https://github.com/johannesvollmer/lebe"
|
||||
|
||||
[profile.bench]
|
||||
lto = true
|
||||
debug = true
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
test = true
|
||||
doctest = true
|
||||
bench = true
|
||||
doc = true
|
||||
plugin = false
|
||||
proc-macro = false
|
||||
|
||||
[[bench]]
|
||||
name = "benches"
|
||||
harness = false
|
||||
|
||||
[dev-dependencies.bencher]
|
||||
version = "0.1.5"
|
||||
|
||||
[dev-dependencies.byteorder]
|
||||
version = "1.4.3"
|
||||
|
||||
[features]
|
||||
|
||||
[badges.maintenance]
|
||||
status = "actively-developed"
|
26
vendor/lebe/LICENSE-BSD-3-Clause
vendored
Normal file
26
vendor/lebe/LICENSE-BSD-3-Clause
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
Copyright (c) 2022 Contributors to the lebe Project. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
90
vendor/lebe/README.md
vendored
Normal file
90
vendor/lebe/README.md
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
[](https://docs.rs/lebe)
|
||||
[](https://crates.io/crates/lebe)
|
||||

|
||||
|
||||
|
||||
# LEBE
|
||||
Tiny, dead simple, high performance endianness conversions with a generic API.
|
||||
This crate purposefully does not have a different method, like `write_u16(my_value)`, for each primitive type. Instead, this uses generic type inference: `write(my_u16)`.
|
||||
|
||||
# Purpose
|
||||
This crate has exactly two purposes:
|
||||
1. Simple conversion between slices of primitives and byte arrays without unsafe code
|
||||
2. Simple and fast conversion from one endianness to the other one
|
||||
|
||||
The [byteorder crate](https://github.com/BurntSushi/byteorder) uses  for this.
|
||||
|
||||
This simplifies reading and writing binary data to files or network streams.
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
Write values.
|
||||
```rust
|
||||
use lebe::io::WriteEndian;
|
||||
use std::io::Write;
|
||||
|
||||
fn main(){
|
||||
let mut output_bytes: Vec<u8> = Vec::new();
|
||||
|
||||
let numbers: &[i32] = &[ 32, 102, 420, 594 ];
|
||||
output_bytes.write_as_little_endian(numbers.len()).unwrap();
|
||||
output_bytes.write_as_little_endian(numbers).unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
Read numbers.
|
||||
```rust
|
||||
use lebe::io::ReadEndian;
|
||||
use std::io::Read;
|
||||
|
||||
fn main(){
|
||||
let mut input_bytes: &[u8] = &[ 3, 244 ];
|
||||
let number: u16 = input_bytes.read_from_little_endian().unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
Read slices.
|
||||
```rust
|
||||
use lebe::io::ReadEndian;
|
||||
use std::io::Read;
|
||||
|
||||
fn main(){
|
||||
let mut input_bytes: &[u8] = &[ 0, 2, 0, 3, 244, 1, 0, 3, 244, 1 ];
|
||||
|
||||
let len: u16 = input_bytes.read_from_little_endian().unwrap();
|
||||
let mut numbers = vec![ 0.0; len as usize ];
|
||||
|
||||
input_bytes.read_from_little_endian_into(numbers.as_mut_slice()).unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
Convert slices in-place.
|
||||
```rust
|
||||
use lebe::Endian;
|
||||
|
||||
fn main(){
|
||||
let mut numbers: &[i32] = &[ 32, 102, 420, 594 ];
|
||||
numbers.convert_current_to_little_endian();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Why not use [byteorder](https://crates.io/crates/byteorder)?
|
||||
This crate supports batch-writing slices with native speed
|
||||
where the os has the matching endianness. Writing slices in `byteorder`
|
||||
must be done manually, and may be slower than expected.
|
||||
This crate does provide u8 and i8 slice operations for completeness.
|
||||
Also, the API of this crate looks simpler.
|
||||
|
||||
# Why not use [endianness](https://crates.io/crates/endianness)?
|
||||
This crate has no runtime costs, just as `byteorder`.
|
||||
|
||||
# Why not use this crate?
|
||||
The other crates probably have better documentation.
|
||||
|
||||
|
||||
# Fun Facts
|
||||
LEBE is made up from 'le' for little endian and 'be' for big endian.
|
||||
If you say that word using english pronounciation,
|
||||
a german might think you said the german word for 'love'.
|
137
vendor/lebe/benches/benches.rs
vendored
Normal file
137
vendor/lebe/benches/benches.rs
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
#[macro_use]
|
||||
extern crate bencher;
|
||||
|
||||
use bencher::Bencher;
|
||||
use lebe::prelude::*;
|
||||
use byteorder::{ReadBytesExt, LittleEndian, BigEndian, WriteBytesExt};
|
||||
use std::io::{Read, Write, Cursor};
|
||||
|
||||
const COUNT_8: usize = 2048;
|
||||
const COUNT_16: usize = COUNT_8 / 2;
|
||||
const COUNT_32: usize = COUNT_8 / 4;
|
||||
const COUNT_64: usize = COUNT_8 / 8;
|
||||
|
||||
|
||||
fn bytes(count: usize) -> Cursor<Vec<u8>> {
|
||||
let vec: Vec<u8> = (0..count).map(|i| (i % 256) as u8).collect();
|
||||
Cursor::new(vec)
|
||||
}
|
||||
|
||||
fn floats(count: usize) -> Vec<f32> {
|
||||
(0..count).map(|i| i as f32).collect()
|
||||
}
|
||||
|
||||
fn read_slice_f32_le_crate(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let mut target = vec![ 0_f32; COUNT_32 ];
|
||||
bencher::black_box(bytes(COUNT_8).read_from_little_endian_into(target.as_mut_slice())).unwrap();
|
||||
bencher::black_box(target);
|
||||
})
|
||||
}
|
||||
|
||||
fn read_slice_f32_le_byteorder(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let mut target = vec![ 0_f32; COUNT_32 ];
|
||||
bencher::black_box(bytes(COUNT_8).read_f32_into::<LittleEndian>(target.as_mut_slice())).unwrap();
|
||||
bencher::black_box(target);
|
||||
})
|
||||
}
|
||||
|
||||
fn read_slice_f32_be_crate(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let mut target = vec![ 0_f32; COUNT_32 ];
|
||||
bencher::black_box(bytes(COUNT_8).read_from_big_endian_into(target.as_mut_slice())).unwrap();
|
||||
bencher::black_box(target);
|
||||
})
|
||||
}
|
||||
|
||||
fn read_slice_f32_be_byteorder(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let mut target = vec![ 0_f32; COUNT_32 ];
|
||||
bencher::black_box(bytes(COUNT_8).read_f32_into::<BigEndian>(target.as_mut_slice())).unwrap();
|
||||
bencher::black_box(target);
|
||||
})
|
||||
}
|
||||
|
||||
// FIXME faster than baseline?!?!!
|
||||
fn write_slice_f32_le_crate(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let data = floats(COUNT_32);
|
||||
let mut output = Vec::with_capacity(COUNT_8);
|
||||
|
||||
bencher::black_box(output.write_as_little_endian(data.as_slice())).unwrap();
|
||||
assert_eq!(output.len(), COUNT_8);
|
||||
bencher::black_box(output);
|
||||
})
|
||||
}
|
||||
|
||||
fn write_slice_f32_le_byteorder(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let data = floats(COUNT_32);
|
||||
let mut output = Vec::with_capacity(COUNT_8);
|
||||
|
||||
for number in data {
|
||||
bencher::black_box(output.write_f32::<LittleEndian>(number)).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(output.len(), COUNT_8);
|
||||
bencher::black_box(output);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
fn write_slice_f32_be_crate(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let data = floats(COUNT_32);
|
||||
let mut output = Vec::with_capacity(COUNT_8);
|
||||
|
||||
bencher::black_box(output.write_as_big_endian(data.as_slice())).unwrap();
|
||||
assert_eq!(output.len(), COUNT_8);
|
||||
bencher::black_box(output);
|
||||
})
|
||||
}
|
||||
|
||||
fn write_slice_f32_be_byteorder(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let data = floats(COUNT_32);
|
||||
let mut output = Vec::with_capacity(COUNT_8);
|
||||
|
||||
for number in data {
|
||||
bencher::black_box(output.write_f32::<BigEndian>(number)).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(output.len(), COUNT_8);
|
||||
bencher::black_box(output);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn read_slice_baseline(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let mut target = vec![ 0_u8; COUNT_8 ];
|
||||
bencher::black_box(bytes(COUNT_8).read_exact(target.as_mut_slice())).unwrap();
|
||||
bencher::black_box(target);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
fn write_slice_baseline(bench: &mut Bencher) {
|
||||
bench.iter(move ||{
|
||||
let data = bytes(COUNT_8).into_inner();
|
||||
let mut output = Vec::with_capacity(COUNT_8);
|
||||
|
||||
bencher::black_box(output.write_all(data.as_slice())).unwrap();
|
||||
bencher::black_box(output);
|
||||
})
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
benches,
|
||||
read_slice_f32_be_byteorder, read_slice_f32_be_crate, read_slice_f32_le_byteorder,
|
||||
read_slice_f32_le_crate, write_slice_f32_le_byteorder, write_slice_f32_le_crate,
|
||||
write_slice_f32_be_byteorder, write_slice_f32_be_crate,
|
||||
read_slice_baseline, write_slice_baseline
|
||||
);
|
||||
|
||||
benchmark_main!(benches);
|
578
vendor/lebe/src/lib.rs
vendored
Normal file
578
vendor/lebe/src/lib.rs
vendored
Normal file
@ -0,0 +1,578 @@
|
||||
#![warn(
|
||||
missing_docs, unused,
|
||||
trivial_numeric_casts,
|
||||
future_incompatible,
|
||||
rust_2018_compatibility,
|
||||
rust_2018_idioms,
|
||||
clippy::all
|
||||
)]
|
||||
|
||||
#![doc(html_root_url = "https://docs.rs/lebe/0.5.0")]
|
||||
|
||||
//! Dead simple endianness conversions.
|
||||
//! The following operations are implemented on
|
||||
//! `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `u128`, `i128`, `f32`, `f64`:
|
||||
//!
|
||||
//!
|
||||
//! ### Read Numbers
|
||||
//! ```rust
|
||||
//! use lebe::prelude::*;
|
||||
//! let mut reader: &[u8] = &[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
|
||||
//!
|
||||
//! let number : u64 = reader.read_from_little_endian()?;
|
||||
//! let number = u64::read_from_big_endian(&mut reader)?;
|
||||
//! # Ok::<(), std::io::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! ### Read Slices
|
||||
//! ```rust
|
||||
//! use std::io::Read;
|
||||
//! use lebe::prelude::*;
|
||||
//! let mut reader: &[u8] = &[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
|
||||
//!
|
||||
//! let mut numbers: &mut [u64] = &mut [0, 0];
|
||||
//! reader.read_from_little_endian_into(numbers)?;
|
||||
//! # Ok::<(), std::io::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! ### Write Numbers
|
||||
//! ```rust
|
||||
//! use std::io::Read;
|
||||
//! use lebe::prelude::*;
|
||||
//! let mut writer: Vec<u8> = Vec::new();
|
||||
//!
|
||||
//! let number: u64 = 1237691;
|
||||
//! writer.write_as_big_endian(&number)?;
|
||||
//! # Ok::<(), std::io::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! ### Write Slices
|
||||
//! ```rust
|
||||
//! use std::io::Write;
|
||||
//! use lebe::prelude::*;
|
||||
//! let mut writer: Vec<u8> = Vec::new();
|
||||
//!
|
||||
//! let numbers: &[u64] = &[1_u64, 234545_u64];
|
||||
//! writer.write_as_little_endian(numbers)?;
|
||||
//! # Ok::<(), std::io::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
|
||||
|
||||
/// Exports some of the most common types.
|
||||
pub mod prelude {
|
||||
pub use super::Endian;
|
||||
pub use super::io::{ WriteEndian, ReadEndian, ReadPrimitive };
|
||||
}
|
||||
|
||||
/// Represents values that can swap their bytes to reverse their endianness.
|
||||
///
|
||||
/// Supports converting values in-place using [`swap_bytes`] or [`convert_current_to_little_endian`]:
|
||||
/// Supports converting while transferring ownership using
|
||||
/// [`from_little_endian_into_current`] or [`from_current_into_little_endian`].
|
||||
///
|
||||
///
|
||||
/// For the types `u8`, `i8`, `&[u8]` and `&[i8]`, this trait will never transform any data,
|
||||
/// as they are just implemented for completeness.
|
||||
pub trait Endian {
|
||||
|
||||
/// Swaps all bytes in this value, inverting its endianness.
|
||||
fn swap_bytes(&mut self);
|
||||
|
||||
/// On a little endian machine, this does nothing.
|
||||
/// On a big endian machine, the bytes of this value are reversed.
|
||||
#[inline] fn convert_current_to_little_endian(&mut self) {
|
||||
#[cfg(target_endian = "big")] {
|
||||
self.swap_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
/// On a big endian machine, this does nothing.
|
||||
/// On a little endian machine, the bytes of this value are reversed.
|
||||
#[inline] fn convert_current_to_big_endian(&mut self) {
|
||||
#[cfg(target_endian = "little")] {
|
||||
self.swap_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
/// On a little endian machine, this does nothing.
|
||||
/// On a big endian machine, the bytes of this value are reversed.
|
||||
#[inline] fn convert_little_endian_to_current(&mut self) {
|
||||
#[cfg(target_endian = "big")] {
|
||||
self.swap_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
/// On a big endian machine, this does nothing.
|
||||
/// On a little endian machine, the bytes of this value are reversed.
|
||||
#[inline] fn convert_big_endian_to_current(&mut self) {
|
||||
#[cfg(target_endian = "little")] {
|
||||
self.swap_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
/// On a little endian machine, this does nothing.
|
||||
/// On a big endian machine, the bytes of this value are reversed.
|
||||
#[inline] fn from_current_into_little_endian(mut self) -> Self where Self: Sized {
|
||||
self.convert_current_to_little_endian();
|
||||
self
|
||||
}
|
||||
|
||||
/// On a big endian machine, this does nothing.
|
||||
/// On a little endian machine, the bytes of this value are reversed.
|
||||
#[inline] fn from_current_into_big_endian(mut self) -> Self where Self: Sized {
|
||||
self.convert_current_to_big_endian();
|
||||
self
|
||||
}
|
||||
|
||||
/// On a little endian machine, this does nothing.
|
||||
/// On a big endian machine, the bytes of this value are reversed.
|
||||
#[inline] fn from_little_endian_into_current(mut self) -> Self where Self: Sized {
|
||||
self.convert_little_endian_to_current();
|
||||
self
|
||||
}
|
||||
|
||||
/// On a big endian machine, this does nothing.
|
||||
/// On a little endian machine, the bytes of this value are reversed.
|
||||
#[inline] fn from_big_endian_into_current(mut self) -> Self where Self: Sized {
|
||||
self.convert_big_endian_to_current();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// call a macro for each argument
|
||||
macro_rules! call_single_arg_macro_for_each {
|
||||
($macro: ident, $( $arguments: ident ),* ) => {
|
||||
$( $macro! { $arguments } )*
|
||||
};
|
||||
}
|
||||
|
||||
// implement this interface for primitive signed and unsigned integers
|
||||
macro_rules! implement_simple_primitive_endian {
|
||||
($type: ident) => {
|
||||
impl Endian for $type {
|
||||
fn swap_bytes(&mut self) {
|
||||
*self = $type::swap_bytes(*self);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
call_single_arg_macro_for_each! {
|
||||
implement_simple_primitive_endian,
|
||||
u16, u32, u64, u128, i16, i32, i64, i128
|
||||
}
|
||||
|
||||
// no-op implementations
|
||||
impl Endian for u8 { fn swap_bytes(&mut self) {} }
|
||||
impl Endian for i8 { fn swap_bytes(&mut self) {} }
|
||||
impl Endian for [u8] { fn swap_bytes(&mut self) {} }
|
||||
impl Endian for [i8] { fn swap_bytes(&mut self) {} }
|
||||
|
||||
// implement this interface for primitive floats, because they do not have a `swap_bytes()` in `std`
|
||||
macro_rules! implement_float_primitive_by_bits {
|
||||
($type: ident) => {
|
||||
impl Endian for $type {
|
||||
fn swap_bytes(&mut self) {
|
||||
*self = Self::from_bits(self.to_bits().swap_bytes());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
implement_float_primitive_by_bits!(f32);
|
||||
implement_float_primitive_by_bits!(f64);
|
||||
|
||||
macro_rules! implement_slice_by_element {
|
||||
($type: ident) => {
|
||||
impl Endian for [$type] {
|
||||
fn swap_bytes(&mut self) {
|
||||
for number in self.iter_mut() { // TODO SIMD?
|
||||
number.swap_bytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
call_single_arg_macro_for_each! {
|
||||
implement_slice_by_element,
|
||||
u16, u32, u64, u128,
|
||||
i16, i32, i64, i128,
|
||||
f64, f32
|
||||
}
|
||||
|
||||
/// Easily write primitives and slices of primitives to
|
||||
/// binary `std::io::Write` streams and easily read from binary `std::io::Read` streams.
|
||||
///
|
||||
/// Also contains the unsafe `bytes` module for reinterpreting values as byte slices and vice versa.
|
||||
pub mod io {
|
||||
use super::Endian;
|
||||
use std::io::{Read, Write, Result};
|
||||
|
||||
/// Reinterpret values as byte slices and byte slices as values unsafely.
|
||||
pub mod bytes {
|
||||
use std::io::{Read, Write, Result};
|
||||
|
||||
/// View this slice of values as a slice of bytes.
|
||||
#[inline]
|
||||
pub unsafe fn slice_as_bytes<T>(value: &[T]) -> &[u8] {
|
||||
std::slice::from_raw_parts(
|
||||
value.as_ptr() as *const u8,
|
||||
value.len() * std::mem::size_of::<T>()
|
||||
)
|
||||
}
|
||||
|
||||
/// View this slice of values as a mutable slice of bytes.
|
||||
#[inline]
|
||||
pub unsafe fn slice_as_bytes_mut<T>(value: &mut [T]) -> &mut [u8] {
|
||||
std::slice::from_raw_parts_mut(
|
||||
value.as_mut_ptr() as *mut u8,
|
||||
value.len() * std::mem::size_of::<T>()
|
||||
)
|
||||
}
|
||||
|
||||
/// View this reference as a slice of bytes.
|
||||
#[inline]
|
||||
pub unsafe fn value_as_bytes<T: Sized>(value: &T) -> &[u8] {
|
||||
std::slice::from_raw_parts(
|
||||
value as *const T as *const u8,
|
||||
std::mem::size_of::<T>()
|
||||
)
|
||||
}
|
||||
|
||||
/// View this reference as a mutable slice of bytes.
|
||||
#[inline]
|
||||
pub unsafe fn value_as_bytes_mut<T: Sized>(value: &mut T) ->&mut [u8] {
|
||||
std::slice::from_raw_parts_mut(
|
||||
value as *mut T as *mut u8,
|
||||
std::mem::size_of::<T>()
|
||||
)
|
||||
}
|
||||
|
||||
/// View this slice as a mutable slice of bytes and write it.
|
||||
#[inline]
|
||||
pub unsafe fn write_slice<T>(write: &mut impl Write, value: &[T]) -> Result<()> {
|
||||
write.write_all(slice_as_bytes(value))
|
||||
}
|
||||
|
||||
/// Read a slice of bytes into the specified slice.
|
||||
#[inline]
|
||||
pub unsafe fn read_slice<T>(read: &mut impl Read, value: &mut [T]) -> Result<()> {
|
||||
read.read_exact(slice_as_bytes_mut(value))
|
||||
}
|
||||
|
||||
/// View this reference as a mutable slice of bytes and write it.
|
||||
#[inline]
|
||||
pub unsafe fn write_value<T: Sized>(write: &mut impl Write, value: &T) -> Result<()> {
|
||||
write.write_all(value_as_bytes(value))
|
||||
}
|
||||
|
||||
/// Read a slice of bytes into the specified reference.
|
||||
#[inline]
|
||||
pub unsafe fn read_value<T: Sized>(read: &mut impl Read, value: &mut T) -> Result<()> {
|
||||
read.read_exact(value_as_bytes_mut(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// A `std::io::Write` output stream which supports writing any primitive values as bytes.
|
||||
/// Will encode the values to be either little endian or big endian, as desired.
|
||||
///
|
||||
/// This extension trait is implemented for all `Write` types.
|
||||
/// Add `use lebe::io::WriteEndian;` to your code
|
||||
/// to automatically unlock this functionality for all types that implement `Write`.
|
||||
pub trait WriteEndian<T: ?Sized> {
|
||||
|
||||
/// Write the byte value of the specified reference, converting it to little endianness
|
||||
fn write_as_little_endian(&mut self, value: &T) -> Result<()>;
|
||||
|
||||
/// Write the byte value of the specified reference, converting it to big endianness
|
||||
fn write_as_big_endian(&mut self, value: &T) -> Result<()>;
|
||||
|
||||
/// Write the byte value of the specified reference, not converting it
|
||||
fn write_as_native_endian(&mut self, value: &T) -> Result<()> {
|
||||
#[cfg(target_endian = "little")] { self.write_as_little_endian(value) }
|
||||
#[cfg(target_endian = "big")] { self.write_as_big_endian(value) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A `std::io::Read` input stream which supports reading any primitive values from bytes.
|
||||
/// Will decode the values from either little endian or big endian, as desired.
|
||||
///
|
||||
/// This extension trait is implemented for all `Read` types.
|
||||
/// Add `use lebe::io::ReadEndian;` to your code
|
||||
/// to automatically unlock this functionality for all types that implement `Read`.
|
||||
pub trait ReadEndian<T: ?Sized> {
|
||||
|
||||
/// Read into the supplied reference. Acts the same as `std::io::Read::read_exact`.
|
||||
fn read_from_little_endian_into(&mut self, value: &mut T) -> Result<()>;
|
||||
|
||||
/// Read into the supplied reference. Acts the same as `std::io::Read::read_exact`.
|
||||
fn read_from_big_endian_into(&mut self, value: &mut T) -> Result<()>;
|
||||
|
||||
/// Read into the supplied reference. Acts the same as `std::io::Read::read_exact`.
|
||||
fn read_from_native_endian_into(&mut self, value: &mut T) -> Result<()> {
|
||||
#[cfg(target_endian = "little")] { self.read_from_little_endian_into(value) }
|
||||
#[cfg(target_endian = "big")] { self.read_from_big_endian_into(value) }
|
||||
}
|
||||
|
||||
/// Read the byte value of the inferred type
|
||||
#[inline]
|
||||
fn read_from_little_endian(&mut self) -> Result<T> where T: Sized + Default {
|
||||
let mut value = T::default();
|
||||
self.read_from_little_endian_into(&mut value)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Read the byte value of the inferred type
|
||||
#[inline]
|
||||
fn read_from_big_endian(&mut self) -> Result<T> where T: Sized + Default {
|
||||
let mut value = T::default();
|
||||
self.read_from_big_endian_into(&mut value)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Read the byte value of the inferred type
|
||||
#[inline]
|
||||
fn read_from_native_endian(&mut self) -> Result<T> where T: Sized + Default {
|
||||
#[cfg(target_endian = "little")] { self.read_from_little_endian() }
|
||||
#[cfg(target_endian = "big")] { self.read_from_big_endian() }
|
||||
}
|
||||
}
|
||||
|
||||
// implement primitive for all types that are implemented by `Read`
|
||||
impl<R: Read + ReadEndian<P>, P: Default> ReadPrimitive<R> for P {}
|
||||
|
||||
|
||||
/// Offers a prettier versions of reading a primitive number.
|
||||
///
|
||||
/// The default way of reading a value is:
|
||||
/// ```rust
|
||||
/// # use std::io::Read;
|
||||
/// # use lebe::prelude::*;
|
||||
/// # let mut reader : &[u8] = &[2, 1];
|
||||
///
|
||||
/// let number: u16 = reader.read_from_little_endian()?;
|
||||
/// println!("{}", number);
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// This trait enables you to use expressions:
|
||||
/// ```rust
|
||||
/// # use std::io::Read;
|
||||
/// # use lebe::prelude::*;
|
||||
/// # let mut reader : &[u8] = &[2, 1];
|
||||
///
|
||||
/// println!("{}", u16::read_from_little_endian(&mut reader)?);
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
/// .
|
||||
///
|
||||
pub trait ReadPrimitive<R: Read + ReadEndian<Self>> : Sized + Default {
|
||||
/// Read this value from the supplied reader. Same as `ReadEndian::read_from_little_endian()`.
|
||||
fn read_from_little_endian(read: &mut R) -> Result<Self> {
|
||||
read.read_from_little_endian()
|
||||
}
|
||||
|
||||
/// Read this value from the supplied reader. Same as `ReadEndian::read_from_big_endian()`.
|
||||
fn read_from_big_endian(read: &mut R) -> Result<Self> {
|
||||
read.read_from_big_endian()
|
||||
}
|
||||
|
||||
/// Read this value from the supplied reader. Same as `ReadEndian::read_from_native_endian()`.
|
||||
fn read_from_native_endian(read: &mut R) -> Result<Self> {
|
||||
read.read_from_native_endian()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! implement_simple_primitive_write {
|
||||
($type: ident) => {
|
||||
impl<W: Write> WriteEndian<$type> for W {
|
||||
fn write_as_little_endian(&mut self, value: &$type) -> Result<()> {
|
||||
unsafe { bytes::write_value(self, &value.from_current_into_little_endian()) }
|
||||
}
|
||||
|
||||
fn write_as_big_endian(&mut self, value: &$type) -> Result<()> {
|
||||
unsafe { bytes::write_value(self, &value.from_current_into_big_endian()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> ReadEndian<$type> for R {
|
||||
#[inline]
|
||||
fn read_from_little_endian_into(&mut self, value: &mut $type) -> Result<()> {
|
||||
unsafe { bytes::read_value(self, value)?; }
|
||||
value.convert_little_endian_to_current();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_from_big_endian_into(&mut self, value: &mut $type) -> Result<()> {
|
||||
unsafe { bytes::read_value(self, value)?; }
|
||||
value.convert_big_endian_to_current();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
call_single_arg_macro_for_each! {
|
||||
implement_simple_primitive_write,
|
||||
u8, u16, u32, u64, u128,
|
||||
i8, i16, i32, i64, i128,
|
||||
f32, f64
|
||||
}
|
||||
|
||||
|
||||
macro_rules! implement_slice_io {
|
||||
($type: ident) => {
|
||||
impl<W: Write> WriteEndian<[$type]> for W {
|
||||
fn write_as_little_endian(&mut self, value: &[$type]) -> Result<()> {
|
||||
#[cfg(target_endian = "big")] {
|
||||
for number in value { // TODO SIMD!
|
||||
self.write_as_little_endian(number)?;
|
||||
}
|
||||
}
|
||||
|
||||
// else write whole slice
|
||||
#[cfg(target_endian = "little")]
|
||||
unsafe { bytes::write_slice(self, value)?; }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_as_big_endian(&mut self, value: &[$type]) -> Result<()> {
|
||||
#[cfg(target_endian = "little")] {
|
||||
for number in value { // TODO SIMD!
|
||||
self.write_as_big_endian(number)?;
|
||||
}
|
||||
}
|
||||
|
||||
// else write whole slice
|
||||
#[cfg(target_endian = "big")]
|
||||
unsafe { bytes::write_slice(self, value)?; }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> ReadEndian<[$type]> for R {
|
||||
fn read_from_little_endian_into(&mut self, value: &mut [$type]) -> Result<()> {
|
||||
unsafe { bytes::read_slice(self, value)? };
|
||||
value.convert_little_endian_to_current();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_from_big_endian_into(&mut self, value: &mut [$type]) -> Result<()> {
|
||||
unsafe { bytes::read_slice(self, value)? };
|
||||
value.convert_big_endian_to_current();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
call_single_arg_macro_for_each! {
|
||||
implement_slice_io,
|
||||
u8, u16, u32, u64, u128,
|
||||
i8, i16, i32, i64, i128,
|
||||
f64, f32
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO: SIMD
|
||||
/*impl<R: Read> ReadEndian<[f32]> for R {
|
||||
fn read_from_little_endian_into(&mut self, value: &mut [f32]) -> Result<()> {
|
||||
unsafe { bytes::read_slice(self, value)? };
|
||||
value.convert_little_endian_to_current();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_from_big_endian_into(&mut self, value: &mut [f32]) -> Result<()> {
|
||||
unsafe { bytes::read_slice(self, value)? };
|
||||
value.convert_big_endian_to_current();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteEndian<[f32]> for W {
|
||||
fn write_as_big_endian(&mut self, value: &[f32]) -> Result<()> {
|
||||
if cfg!(target_endian = "little") {
|
||||
|
||||
// FIX ME this SIMD optimization makes no difference ... why? like, ZERO difference, not even worse
|
||||
// #[cfg(feature = "simd")]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
unsafe {
|
||||
if is_x86_feature_detected!("avx2") {
|
||||
write_bytes_avx(self, value);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise (no avx2 available)
|
||||
// for number in value {
|
||||
// self.write_as_little_endian(number);
|
||||
// }
|
||||
//
|
||||
// return Ok(());
|
||||
unimplemented!();
|
||||
|
||||
#[target_feature(enable = "avx2")]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
unsafe fn write_bytes_avx(write: &mut impl Write, slice: &[f32]) -> Result<()> {
|
||||
#[cfg(target_arch = "x86")] use std::arch::x86 as mm;
|
||||
#[cfg(target_arch = "x86_64")] use std::arch::x86_64 as mm;
|
||||
|
||||
let bytes: &[u8] = crate::io::bytes::slice_as_bytes(slice);
|
||||
let mut chunks = bytes.chunks_exact(32);
|
||||
|
||||
let indices = mm::_mm256_set_epi8(
|
||||
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
|
||||
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
|
||||
// 3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12,
|
||||
// 3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12
|
||||
);
|
||||
|
||||
for chunk in &mut chunks {
|
||||
let data = mm::_mm256_loadu_si256(chunk.as_ptr() as _);
|
||||
let result = mm::_mm256_shuffle_epi8(data, indices);
|
||||
let mut out = [0_u8; 32];
|
||||
mm::_mm256_storeu_si256(out.as_mut_ptr() as _, result);
|
||||
write.write_all(&out)?;
|
||||
}
|
||||
|
||||
let remainder = chunks.remainder();
|
||||
|
||||
{ // copy remainder into larger slice, with zeroes at the end
|
||||
let mut last_chunk = [0_u8; 32];
|
||||
last_chunk[0..remainder.len()].copy_from_slice(remainder);
|
||||
let data = mm::_mm256_loadu_si256(last_chunk.as_ptr() as _);
|
||||
let result = mm::_mm256_shuffle_epi8(data, indices);
|
||||
mm::_mm256_storeu_si256(last_chunk.as_mut_ptr() as _, result);
|
||||
write.write_all(&last_chunk[0..remainder.len()])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
unsafe { bytes::write_slice(self, value)?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_as_little_endian(&mut self, value: &[f32]) -> Result<()> {
|
||||
for number in value {
|
||||
self.write_as_little_endian(number)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
213
vendor/lebe/tests/tests.rs
vendored
Normal file
213
vendor/lebe/tests/tests.rs
vendored
Normal file
@ -0,0 +1,213 @@
|
||||
extern crate lebe;
|
||||
|
||||
use lebe::prelude::*;
|
||||
use std::mem;
|
||||
|
||||
use byteorder::{WriteBytesExt, LittleEndian, BigEndian, ReadBytesExt};
|
||||
|
||||
#[test]
|
||||
fn make_le_u32_slice() {
|
||||
// as seen on https://doc.rust-lang.org/std/primitive.u32.html#method.to_le
|
||||
let n = 0x1Au32;
|
||||
|
||||
let mut n_le = [n];
|
||||
n_le.convert_current_to_little_endian();
|
||||
|
||||
if cfg!(target_endian = "little") {
|
||||
assert_eq!(n_le, [n])
|
||||
}
|
||||
else {
|
||||
assert_eq!(n_le, [u32::swap_bytes(n)])
|
||||
}
|
||||
|
||||
// assert_eq!(n_le, byteorder::LittleEndian::from_)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_be_u32_slice() {
|
||||
// as seen on https://doc.rust-lang.org/std/primitive.u32.html#method.to_be
|
||||
let n = 0x1Au32;
|
||||
|
||||
let mut n_be = [n];
|
||||
n_be.convert_current_to_big_endian();
|
||||
|
||||
if cfg!(target_endian = "big") {
|
||||
assert_eq!(n_be, [n])
|
||||
}
|
||||
else {
|
||||
assert_eq!(n_be, [n.swap_bytes()])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_le_u16_slice() {
|
||||
// as seen on https://doc.rust-lang.org/std/primitive.u16.html#method.to_le
|
||||
let n = 0x1Au16;
|
||||
|
||||
let mut n_le = [n];
|
||||
n_le.convert_current_to_little_endian();
|
||||
|
||||
if cfg!(target_endian = "little") {
|
||||
assert_eq!(n_le, [n])
|
||||
}
|
||||
else {
|
||||
assert_eq!(n_le, [n.swap_bytes()])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_le_i64_slice() {
|
||||
// as seen on https://doc.rust-lang.org/std/primitive.u64.html#method.to_be
|
||||
let n1 = 0x14F3EEBCCD93895A_i64;
|
||||
let n2 = 0x114F3EF99B81CC5A_i64;
|
||||
|
||||
let mut n_be = [n1, n2];
|
||||
n_be.convert_current_to_big_endian();
|
||||
|
||||
if cfg!(target_endian = "big") {
|
||||
assert_eq!(n_be, [n1, n2])
|
||||
}
|
||||
else {
|
||||
assert_eq!(n_be, [n1.swap_bytes(), n2.swap_bytes()])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_be_f64() {
|
||||
let i = 0x14F3EEBCCD93895A_u64;
|
||||
|
||||
let mut f: f64 = unsafe { mem::transmute(i) };
|
||||
f.convert_current_to_big_endian();
|
||||
|
||||
assert_eq!(f, unsafe { mem::transmute(i.to_be()) })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_be_f64() {
|
||||
let i = 0x14F3EEBCCD93895A_u64;
|
||||
|
||||
let f: f64 = unsafe { mem::transmute(i) };
|
||||
let f = f.from_current_into_big_endian();
|
||||
|
||||
assert_eq!(f, unsafe { mem::transmute(i.to_be()) })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_be_i16() {
|
||||
let i = 0x195A_i16;
|
||||
let be = i.from_current_into_big_endian();
|
||||
|
||||
if cfg!(target_endian = "big") {
|
||||
assert_eq!(be, i)
|
||||
}
|
||||
else {
|
||||
assert_eq!(be, i.swap_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_be_u32() {
|
||||
let i = 0x1220943_u32;
|
||||
let be = i.from_current_into_big_endian();
|
||||
|
||||
if cfg!(target_endian = "big") {
|
||||
assert_eq!(be, i)
|
||||
}
|
||||
else {
|
||||
assert_eq!(be, i.swap_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmp_read_be_u16() {
|
||||
let read: &[u8] = &[0x33, 0xbb];
|
||||
let a = u16::read_from_big_endian(&mut read.clone()).unwrap();
|
||||
let b: u16 = read.clone().read_from_big_endian().unwrap();
|
||||
let c = read.clone().read_u16::<BigEndian>().unwrap();
|
||||
|
||||
assert_eq!(a, b);
|
||||
assert_eq!(a, c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmp_read_le_u16() {
|
||||
let read: &[u8] = &[0x33, 0xbb];
|
||||
let a = u16::read_from_little_endian(&mut read.clone()).unwrap();
|
||||
let b: u16 = read.clone().read_from_little_endian().unwrap();
|
||||
let c = read.clone().read_u16::<LittleEndian>().unwrap();
|
||||
|
||||
assert_eq!(a, b);
|
||||
assert_eq!(a, c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmp_read_le_f32() {
|
||||
let read: &[u8] = &[0x33, 0xBB, 0x44, 0xCC];
|
||||
let a = f32::read_from_little_endian(&mut read.clone()).unwrap();
|
||||
let b: f32 = read.clone().read_from_little_endian().unwrap();
|
||||
let c = read.clone().read_f32::<LittleEndian>().unwrap();
|
||||
|
||||
assert_eq!(a, b);
|
||||
assert_eq!(a, c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmp_read_be_slice() {
|
||||
let mut write_expected = Vec::new();
|
||||
let mut write_actual = Vec::new();
|
||||
|
||||
let data: Vec<f32> = (0..31*31).map(|i| i as f32).collect();
|
||||
|
||||
for number in &data {
|
||||
write_expected.write_f32::<BigEndian>(*number).unwrap();
|
||||
}
|
||||
|
||||
write_actual.write_as_big_endian(data.as_slice()).unwrap();
|
||||
assert_eq!(write_actual, write_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmp_write_le_slice() {
|
||||
let mut write_expected = Vec::new();
|
||||
let mut write_actual = Vec::new();
|
||||
|
||||
let data: Vec<f32> = (0..31*31).map(|i| i as f32).collect();
|
||||
|
||||
for number in &data {
|
||||
write_expected.write_f32::<LittleEndian>(*number).unwrap();
|
||||
}
|
||||
|
||||
write_actual.write_as_little_endian(data.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(write_actual, write_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmp_write_le_u32() {
|
||||
let mut write_expected = Vec::new();
|
||||
let mut write_actual = Vec::new();
|
||||
|
||||
let data = 0x23573688_u32;
|
||||
write_expected.write_u32::<LittleEndian>(data).unwrap();
|
||||
write_actual.write_as_little_endian(&data).unwrap();
|
||||
|
||||
assert_eq!(write_actual, write_expected);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn cmp_write_le_slice_u64() {
|
||||
let mut write_expected = Vec::new();
|
||||
let mut write_actual = Vec::new();
|
||||
|
||||
let data: Vec<u64> = (1000..1000+310*31).map(|i| i as u64).collect();
|
||||
|
||||
for number in &data {
|
||||
write_expected.write_u64::<LittleEndian>(*number).unwrap();
|
||||
}
|
||||
|
||||
write_actual.write_as_little_endian(data.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(write_actual, write_expected);
|
||||
}
|
Reference in New Issue
Block a user