Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
e14e449
Add CompactEncodable trait
cowlicks Nov 8, 2024
dfaa143
implement CompactEncodable for Vec<T>
cowlicks Dec 30, 2024
f5bf11c
just EncodingError
cowlicks Dec 30, 2024
945e8aa
make encoded_bytes take buffer
cowlicks Dec 30, 2024
def5bae
Add usize encoding for compactencodable
cowlicks Jan 1, 2025
df76e71
Add IpAddr encodings
cowlicks Jan 1, 2025
b81646f
impl Clone, PartialEq, and Error for EncodingError
cowlicks Apr 3, 2025
de6c4fd
docs
cowlicks Apr 3, 2025
23ddffc
take `&[T]` instead of `&Vec<T>` in funcs
cowlicks Apr 3, 2025
2a28141
handle errors, fix todo!()
cowlicks Apr 5, 2025
0b6cadb
Add helper funcs for building EncodingError
cowlicks Apr 10, 2025
de293d9
Add encodable module
cowlicks Apr 10, 2025
2caaf37
Add conversion for Encoding to Encodable
cowlicks Apr 10, 2025
ff969aa
lints
cowlicks Apr 10, 2025
3636574
refactoring
cowlicks Apr 10, 2025
d72f8bd
Remove EncodedSize trai
cowlicks Apr 10, 2025
75020e3
fix todo!()
cowlicks Apr 10, 2025
5a23165
begin adding encodable tests
cowlicks Apr 10, 2025
9c2f085
wip new encoding tests
cowlicks Apr 12, 2025
108f7ca
Fix issues with implementing for String & str
cowlicks Apr 12, 2025
6625a60
Fix bug in u32
cowlicks Apr 12, 2025
37fe9c4
redo all tests
cowlicks Apr 12, 2025
f24c455
Add VecEncodable for u32
cowlicks Apr 14, 2025
36ae9ec
add CompactEncodable::create_buffer
cowlicks Apr 14, 2025
8ae2153
lints
cowlicks Apr 14, 2025
9d0b6bd
flush out macros and add doctests
cowlicks Apr 14, 2025
2f53078
better docs
cowlicks Apr 14, 2025
c7bf377
add box_ & vec_ prefix to VecEnc and BoxEnc trait methods
cowlicks Apr 14, 2025
9431df9
Rename encoded_bytes to encode
cowlicks Apr 14, 2025
52185b2
rename CompactEncodable to CompacEncoding
cowlicks Apr 15, 2025
ab43259
renames
cowlicks Apr 15, 2025
5171bb8
rewrite module documentation
cowlicks Apr 15, 2025
23b1359
cargo +nightly rustfmt
cowlicks Apr 15, 2025
58c54b5
replace lib.rs
cowlicks Apr 15, 2025
ff5a614
rm unused
cowlicks Apr 15, 2025
e089aa9
rm unused
cowlicks Apr 15, 2025
abcca34
fix problems from move
cowlicks Apr 15, 2025
4b29f6d
rm unused
cowlicks Apr 15, 2025
a520454
rename types -> errors
cowlicks Apr 15, 2025
c633b5c
rename and reorder
cowlicks Apr 15, 2025
9dc20df
Add FixedWidthENcoding for uints
cowlicks Apr 16, 2025
eda12e1
docs & rm debug & add as_array
cowlicks Apr 17, 2025
bab285f
dedup decode_usize
cowlicks Apr 21, 2025
f3ec0fb
more docs and clippy
cowlicks Apr 21, 2025
2ed7ae3
Add EncodingError::external
cowlicks Apr 30, 2025
8ec7727
Add Cenc::encode/decode_with_len
cowlicks Apr 30, 2025
d398358
simplify summing in macros
cowlicks Apr 30, 2025
8234211
Add fn for creating Box<[u8]> buffer
cowlicks Apr 30, 2025
de0a24c
Fix benches to work with stuff
cowlicks Apr 30, 2025
4ec35b4
format doc comments code
cowlicks Apr 30, 2025
ed621f4
BoxArrayEnc -> BoxedSliceEnc
cowlicks May 1, 2025
329bf32
Use Box<[u8]> for buffer instead of Vec<u8>
cowlicks May 1, 2025
70471c5
better doc comments
cowlicks May 1, 2025
1289a93
fix benches
cowlicks May 2, 2025
a7f67ac
more doc tests
cowlicks May 2, 2025
d1d597f
don't re-export error module
cowlicks May 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 22 additions & 27 deletions benches/simple.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
use std::time::Instant;

use compact_encoding::{CompactEncoding, State};
use compact_encoding::{map_decode, map_encode, sum_encoded_size, CompactEncoding, EncodingError};
use criterion::{black_box, criterion_group, criterion_main, Criterion};

const U32_VALUE: u32 = 0xF0E1D2C3;
const STR_VALUE: &str = "foo";
const U64_VALUE: u64 = u64::MAX;

fn preencode() -> State {
let mut enc_state = State::new();
enc_state.preencode(&U32_VALUE).unwrap();
enc_state.preencode_str(STR_VALUE).unwrap();
enc_state.preencode(&U64_VALUE).unwrap();
enc_state
fn preencode() -> Result<usize, EncodingError> {
Ok(sum_encoded_size!(U32_VALUE, STR_VALUE, U64_VALUE))
}

fn encode(enc_state: &mut State, buffer: &mut [u8]) {
enc_state.encode(&U32_VALUE, buffer).unwrap();
enc_state.encode_str(STR_VALUE, buffer).unwrap();
enc_state.encode(&U64_VALUE, buffer).unwrap();
fn encode(buffer: &mut [u8]) -> Result<(), EncodingError> {
let _ = map_encode!(buffer, U32_VALUE, STR_VALUE, U64_VALUE);
Ok(())
}

fn decode(dec_state: &mut State, buffer: &[u8]) {
let _: u32 = dec_state.decode(buffer).unwrap();
let _: String = dec_state.decode(buffer).unwrap();
let _: u64 = dec_state.decode(buffer).unwrap();
fn decode(buffer: &[u8]) -> Result<(), EncodingError> {
map_decode!(buffer, [u32, String, u64]);
Ok(())
}

fn create_buffer(encoded_size: usize) -> Box<[u8]> {
vec![0; encoded_size].into_boxed_slice()
}

fn preencode_generic_simple(c: &mut Criterion) {
Expand All @@ -36,10 +34,10 @@ fn preencode_generic_simple(c: &mut Criterion) {
fn create_buffer_generic_simple(c: &mut Criterion) {
c.bench_function("create buffer generic simple", |b| {
b.iter_custom(|iters| {
let enc_state = preencode();
let encoded_size = preencode().unwrap();
let start = Instant::now();
for _ in 0..iters {
black_box(enc_state.create_buffer());
black_box(create_buffer(encoded_size));
}
start.elapsed()
});
Expand All @@ -50,13 +48,12 @@ fn create_buffer_generic_simple(c: &mut Criterion) {
fn encode_generic_simple(c: &mut Criterion) {
c.bench_function("encode generic simple", |b| {
b.iter_custom(|iters| {
let enc_state = preencode();
let buffer = enc_state.create_buffer();
let encoded_size = preencode().unwrap();
let buffer = create_buffer(encoded_size);
let start = Instant::now();
for _ in 0..iters {
let mut enc_state = enc_state.clone();
let mut buffer = buffer.clone();
black_box(encode(&mut enc_state, &mut buffer));
black_box(encode(&mut buffer).unwrap());
}
start.elapsed()
});
Expand All @@ -67,15 +64,13 @@ fn encode_generic_simple(c: &mut Criterion) {
fn decode_generic_simple(c: &mut Criterion) {
c.bench_function("decode generic simple", |b| {
b.iter_custom(|iters| {
let mut enc_state = preencode();
let mut buffer = enc_state.create_buffer();
encode(&mut enc_state, &mut buffer);
let dec_state = State::from_buffer(&buffer);
let encoded_size = preencode().unwrap();
let mut buffer = vec![0_u8; encoded_size];
encode(&mut buffer).unwrap();
let start = Instant::now();
for _ in 0..iters {
let mut dec_state = dec_state.clone();
let buffer = buffer.clone();
black_box(decode(&mut dec_state, &buffer));
black_box(decode(&buffer).unwrap());
}
start.elapsed()
});
Expand Down
93 changes: 93 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! Basic types of compact_encoding.
use std::fmt;

/// Specific type [EncodingError]
#[derive(fmt::Debug, Clone, PartialEq)]
pub enum EncodingErrorKind {
/// Encoding or decoding did not stay within the bounds of the buffer
OutOfBounds,
/// Buffer data overflowed type during encoding or decoding.
Overflow,
/// Buffer contained invalid data during decoding.
InvalidData,
/// Some external error occurred causing a [`crate::CompactEncoding`] method to fail.
External,
}

/// Encoding/decoding error.
#[derive(fmt::Debug, Clone, PartialEq)]
pub struct EncodingError {
/// Specific type of error
pub kind: EncodingErrorKind,
/// Message for the error
pub message: String,
}

impl std::error::Error for EncodingError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}

impl EncodingError {
/// Create EncodingError
pub fn new(kind: EncodingErrorKind, message: &str) -> Self {
Self {
kind,
message: message.to_string(),
}
}
/// Helper function for making an overflow error
pub fn overflow(message: &str) -> Self {
Self {
kind: EncodingErrorKind::Overflow,
message: message.to_string(),
}
}
/// Helper function for making an out of bounds error
pub fn out_of_bounds(message: &str) -> Self {
Self {
kind: EncodingErrorKind::OutOfBounds,
message: message.to_string(),
}
}
/// Helper function for making an invalid data error
pub fn invalid_data(message: &str) -> Self {
Self {
kind: EncodingErrorKind::InvalidData,
message: message.to_string(),
}
}
/// Helper function for making an invalid data error
pub fn external(message: &str) -> Self {
Self {
kind: EncodingErrorKind::External,
message: message.to_string(),
}
}
}

impl fmt::Display for EncodingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let prefix = match self.kind {
EncodingErrorKind::OutOfBounds => "Compact encoding failed, out of bounds",
EncodingErrorKind::Overflow => "Compact encoding failed, overflow",
EncodingErrorKind::InvalidData => "Compact encoding failed, invalid data",
EncodingErrorKind::External => {
"An external error caused a compact encoding operation to fail"
}
};
write!(f, "{}: {}", prefix, self.message,)
}
}

impl From<EncodingError> for std::io::Error {
fn from(e: EncodingError) -> Self {
match e.kind {
EncodingErrorKind::InvalidData => {
std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{e}"))
}
_ => std::io::Error::new(std::io::ErrorKind::Other, format!("{e}")),
}
}
}
164 changes: 164 additions & 0 deletions src/fixedwidth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//! Allow encoding of unsigned ints in a fixed width way, instead of the default variable width.
//!
//! Why? Because the default [`CompactEncoding`] implementation for unsigned integers uses a
//! variable width encoding. However sometimes want them encoded with a fixed width,
//! [`FixedWidthEncoding`] lets us do this. To fixed width encode an unsigned integrer simply call
//! [`FixedWidthEncoding::as_fixed_width`] on it. Like this:
//! ```
//! # use compact_encoding::EncodingError;
//! use compact_encoding::{to_encoded_bytes, FixedWidthEncoding};
//! let buff = to_encoded_bytes!(42u32.as_fixed_width());
//! assert_eq!(buff, [42, 0, 0, 0].into());
//! // vs variable width
//! let buff = to_encoded_bytes!(42u32);
//! assert_eq!(buff, [42].into());
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! Likewise when decoding decoding from a fixed width encoded buffer you use
//! [`FixedWidthUint::decode`] which will decode to the underlying unsigned integer type.
//! So: `FixedWidthUint<u32>::decode(buffer) -> u32`.
//! Note that we also provide type aliases to make this more ergonomic:
//! `FixedWidthU64 = FixedWidthUint<u64`.
//!
//! ```
//! # use compact_encoding::EncodingError;
//! use compact_encoding::{map_decode, FixedWidthU32, FixedWidthUint};
//! let buff = vec![42, 0, 0, 0]; // 42_u32 fixed width encoded
//!
//! let ((decoded,), _) = map_decode!(&buff, [FixedWidthUint<u32>]);
//! assert_eq!(decoded, 42); // NOT! FixedWidthUint(42_u32)
//!
//! assert_eq!(map_decode!(&buff, [FixedWidthU32]).0 .0, 42); // or using the alias
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```

use crate::{
decode_u32, decode_u64, encode_u32, encode_u64, CompactEncoding, EncodingError, U32_SIZE,
U64_SIZE,
};

/// Implents functionality needed to encode unisegned integrer in a fixed width way with
/// [`CompactEncoding`]
pub trait FixedWidthEncoding {
/// The type we decode to
// TODO could we just use T?
type Decode;
/// The size in bytes required to encode `self`.
fn fw_encoded_size(&self) -> Result<usize, EncodingError>;

/// Encode `self` into `buffer` returning the remainder of `buffer`.
fn fw_encode<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], EncodingError>;

/// Decode a value from the given `buffer` of the type specified by the `Decode` type parameter
/// (which defaults to `Self`). Returns the decoded value and remaining undecoded bytes.
fn fw_decode(buffer: &[u8]) -> Result<(Self::Decode, &[u8]), EncodingError>
where
Self: Sized;

/// Get a uint in a form that encodes to a fixed width
fn as_fixed_width(&self) -> FixedWidthUint<Self> {
FixedWidthUint(self)
}
}

/// A fixed width encodable unsigned integer
#[derive(Debug)]
pub struct FixedWidthUint<'a, T: FixedWidthEncoding + ?Sized>(&'a T);

impl<T: FixedWidthEncoding> CompactEncoding<T::Decode> for FixedWidthUint<'_, T> {
fn encoded_size(&self) -> Result<usize, EncodingError> {
self.0.fw_encoded_size()
}

fn encode<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], EncodingError> {
self.0.fw_encode(buffer)
}

fn decode(buffer: &[u8]) -> Result<(T::Decode, &[u8]), EncodingError>
where
Self: Sized,
{
<T as FixedWidthEncoding>::fw_decode(buffer)
}
}

impl FixedWidthEncoding for u32 {
type Decode = u32;

fn fw_encoded_size(&self) -> Result<usize, EncodingError> {
Ok(U32_SIZE)
}

fn fw_encode<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], EncodingError> {
encode_u32(*self, buffer)
}

fn fw_decode(buffer: &[u8]) -> Result<(Self::Decode, &[u8]), EncodingError>
where
Self: Sized,
{
decode_u32(buffer)
}
}
impl FixedWidthEncoding for u64 {
type Decode = u64;

fn fw_encoded_size(&self) -> Result<usize, EncodingError> {
Ok(U64_SIZE)
}

fn fw_encode<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], EncodingError> {
encode_u64(*self, buffer)
}

fn fw_decode(buffer: &[u8]) -> Result<(Self::Decode, &[u8]), EncodingError>
where
Self: Sized,
{
decode_u64(buffer)
}
}

/// A wrapper around [`u32`] to let us encoded/decode to/from a fixed width
pub type FixedWidthU32<'a> = FixedWidthUint<'a, u32>;
/// A wrapper around [`u64`] to let us encoded/decode to/from a fixed width
pub type FixedWidthU64<'a> = FixedWidthUint<'a, u64>;
#[cfg(test)]
mod test {
use crate::{map_decode, to_encoded_bytes};

use super::*;

#[test]
fn fixed_width_u32() -> Result<(), EncodingError> {
let x = 42u32;
let fixed_buff = to_encoded_bytes!(x.as_fixed_width());
let var_buff = to_encoded_bytes!(x);
assert_eq!(fixed_buff, [42_u8, 0, 0, 0].into());
assert_eq!(var_buff, [42_u8].into());

let ((fixed_dec,), rest) = map_decode!(&fixed_buff, [FixedWidthU32]);
assert!(rest.is_empty());
assert_eq!(fixed_dec, x);

let ((var_dec,), rest) = map_decode!(&var_buff, [u32]);
assert!(rest.is_empty());
assert_eq!(var_dec, x);
Ok(())
}

#[test]
fn fixed_width_u64() -> Result<(), EncodingError> {
let x = 42u64;
let fixed_buff = to_encoded_bytes!(x.as_fixed_width());
let var_buff = to_encoded_bytes!(x);
assert_eq!(fixed_buff, [42, 0, 0, 0, 0, 0, 0, 0].into());
assert_eq!(var_buff, [42].into());

let ((fixed_dec,), rest) = map_decode!(&fixed_buff, [FixedWidthU64]);
assert!(rest.is_empty());
assert_eq!(fixed_dec, x);
Ok(())
}
}
Loading
Loading