Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update packable documentation #20

Merged
merged 7 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
ubuntu-latest-${{ matrix.rust }}-${{ env.cache-name }}-
ubuntu-latest-${{ matrix.rust }}-
ubuntu-latest-

- name: Check
uses: actions-rs/cargo@v1
with:
Expand Down Expand Up @@ -312,7 +312,7 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: doc
args: --no-deps --document-private-items
args: --no-deps --document-private-items --all-features

missing-files:
runs-on: ubuntu-latest
Expand Down
83 changes: 40 additions & 43 deletions packable/packable/README.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,59 @@
# packable
Packable is a binary serialization and deserialization framework.

A crate that provides a [`Packable`] trait to serialize and deserialize types.
## Design

For more information about the design of this crate please read the [`Packable`], [`unpacker`], [`packer`],
[`UnpackError`](error::UnpackError) and [`UnpackErrorExt`](error::UnpackErrorExt) documentation.
Values of a type can be serialized and deserialized if the type implements the
`Packable` trait. The serialization strategy used for each type is up to the
user. However, `Packable` can also be derived, this provides a cosistent
pvdrz marked this conversation as resolved.
Show resolved Hide resolved
serialization strategy.

## Motivation
For more information about the design of this crate please read the `Packable`,
`unpacker`, `packer`, `UnpackError` and `UnpackErrorExt` documentation.

This crate was written as a `no_std` replacement for the
[`Packable` serialization framework](https://github.com/iotaledger/bee/blob/c08f1bac7170fe5cb650ddc347ac4d483bb9036a/bee-common/bee-common/src/packable.rs)
used in the Bee node implementation for Chrysalis Part 2.
## `no_std` compatibility

### The old `Packable` trait
Packable is `no_std` compatible. This is achieved by introducing the `Packer`
and `Unpacker` taits to abstract away any IO operation without relying on
pvdrz marked this conversation as resolved.
Show resolved Hide resolved
`std::io`. This has the additional benefit of allowing us to pack and unpack
values from different kinds of buffers.

The need for a serialization API existed before Coordicide. Efforts to satisfy this need culminated with the
introduction of the `Packable` trait in the `bee-common` crate during the Chrysalis part 2 period.
Most of the design decisions behind this crate were done to simplify the serialization of the
[IOTA protocol messages](https://github.com/iotaledger/protocol-rfcs/pull/0017).
The proposed trait was the following:
## Types that implement `Packable`

```rust
use std::io::{Read, Write};
The `Packable` trait is implemented for every sized integer type by encoding
the value as an array of bytes in little-endian order.

pub trait Packable {
type Error: std::fmt::Debug;
Booleans are packed following Rust's data layout, meaning that `true` is packed
as a `1` byte and `false` as a `0` byte. However, boolean unpacking is less
strict and unpacks any non-zero byte as `true`.

fn packed_len(&self) -> usize;
Types such as `Box<[T]>`, `[T; N]` and `Option<T>` implement `Packable` if T
pvdrz marked this conversation as resolved.
Show resolved Hide resolved
implements `Packable`.

fn pack<W: Write>(&self, writer: &mut W) -> Result<(), Self::Error>;
This crate also provides bounded integers under the `bounded` module which have
additional syntactical checks to guarantee that the deserialized values are
in-bounds. It is also possible to serialize and deserialize sequences of values
by using the types provided in the `prefix` module, which represent linear
sequences of valeus with a length prefix.
pvdrz marked this conversation as resolved.
Show resolved Hide resolved

fn unpack_inner<R: Read + ?Sized, const CHECK: bool>(reader: &mut R) -> Result<Self, Self::Error>
where
Self: Sized;
}
```
The main issue with this trait is that it cannot be used in a `no_std` environment because it depends explicitly on
the [`std::io`] API, whose transition to the [`core`] crate has not been decided yet.
Another issue is that the `Error` type is used to represent three different kinds of errors:
Check the `Packable` `impl` section for further information.

- Writing errors: Raised when there are issues while writing bytes.
- Reading errors: Raised when there are issues while reading bytes.
- Deserialization errors: Raised when the bytes being used to create a value are invalid for the data layout of such
value.
## Features

## Replacing [`std::io`]
### `std`

We introduced the [`Packer`](packer::Packer) and [`Unpacker`](unpacker::Unpacker) taits to abstract away any IO
operation without relying on [`std::io`]. This has the additional benefit of allowing us to pack and unpack values
from different kinds of buffers.
This feature implements `Error` for all the error types provided by this crate.

## Types that implement [`Packable`]
### `io`

The [`Packable`] trait is implemented for every integer type by encoding the value as an array of bytes in
little-endian order. Booleans are packed following Rust's data layout, meaning that `true` is packed as a `1` byte
and `false` as a `0` byte. However, boolean unpacking is less strict and unpacks any non-zero byte as `true`.
Additional implementations of [`Packable`] are provided for [`Vec<T>`](std::vec::Vec), `Box<[T]>`, `[T; N]` and
[`Option<T>`] if T implements [`Packable`].
This feature provides the types `IoPacker` and `IoUnpacker` which allow packing
and unpacking from values whose types implement `Write` and `Read`
respectively.

Check the [`Packable`] `impl` section for further information.
### `usize`

This featue implements `Packable` for `usize`, `isize`, `Vec<T>`, `Box<[T]>`,
pvdrz marked this conversation as resolved.
Show resolved Hide resolved
pvdrz marked this conversation as resolved.
Show resolved Hide resolved
`String` this is done serializing and deserializing pointer sized integers as
pvdrz marked this conversation as resolved.
Show resolved Hide resolved
64-bit integers. This feature will not work for targets with a pointer width
larger than 64.

License: Apache-2.0
83 changes: 41 additions & 42 deletions packable/packable/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,63 @@
// Copyright 2021-2022 IOTA Stiftung
pvdrz marked this conversation as resolved.
Show resolved Hide resolved
// SPDX-License-Identifier: Apache-2.0

//! A crate that provides a [`Packable`] trait to serialize and deserialize types.
//! Packable is a binary serialization and deserialization framework.
//!
//! For more information about the design of this crate please read the [`Packable`], [`unpacker`], [`packer`],
//! [`UnpackError`](error::UnpackError) and [`UnpackErrorExt`](error::UnpackErrorExt) documentation.
//! # Design
//!
//! # Motivation
//! Values of a type can be serialized and deserialized if the type implements the [`Packable`]
//! trait. The serialization strategy used for each type is up to the user. However, [`Packable`]
//! can also be derived, this provides a cosistent serialization strategy.
//!
//! This crate was written as a `no_std` replacement for the
//! [`Packable` serialization framework](https://github.com/iotaledger/bee/blob/c08f1bac7170fe5cb650ddc347ac4d483bb9036a/bee-common/bee-common/src/packable.rs)
//! used in the Bee node implementation for Chrysalis Part 2.
//! For more information about the design of this crate please read the [`Packable`], [`unpacker`],
//! [`packer`], [`UnpackError`](error::UnpackError) and [`UnpackErrorExt`](error::UnpackErrorExt)
//! documentation.
//!
//! ## The old `Packable` trait
//! # `no_std` compatibility
//!
//! The need for a serialization API existed before Coordicide. Efforts to satisfy this need culminated with the
//! introduction of the `Packable` trait in the `bee-common` crate during the Chrysalis part 2 period.
//! Most of the design decisions behind this crate were done to simplify the serialization of the
//! [IOTA protocol messages](https://github.com/iotaledger/protocol-rfcs/pull/0017).
//! The proposed trait was the following:
//! Packable is `no_std` compatible. This is achieved by introducing the [`Packer`](packer::Packer)
//! and [`Unpacker`](unpacker::Unpacker) taits to abstract away any IO operation without relying on
//! [`std::io`]. This has the additional benefit of allowing us to pack and unpack values from
//! different kinds of buffers.
//!
//! ```
//! use std::io::{Read, Write};
//! # Types that implement [`Packable`]
//!
//! pub trait Packable {
//! type Error: std::fmt::Debug;
//! The [`Packable`] trait is implemented for every sized integer type by encoding the value as an
//! array of bytes in little-endian order.
//!
//! fn packed_len(&self) -> usize;
//! Booleans are packed following Rust's data layout, meaning that `true` is packed as a `1` byte
//! and `false` as a `0` byte. However, boolean unpacking is less strict and unpacks any non-zero
//! byte as `true`.
//!
//! fn pack<W: Write>(&self, writer: &mut W) -> Result<(), Self::Error>;
//! Types such as `Box<[T]>`, `[T; N]` and [`Option<T>`] implement [`Packable`] if T implements
//! [`Packable`].
//!
//! fn unpack_inner<R: Read + ?Sized, const CHECK: bool>(reader: &mut R) -> Result<Self, Self::Error>
//! where
//! Self: Sized;
//! }
//! ```
//! The main issue with this trait is that it cannot be used in a `no_std` environment because it depends explicitly on
//! the [`std::io`] API, whose transition to the [`core`] crate has not been decided yet.
//! Another issue is that the `Error` type is used to represent three different kinds of errors:
//! This crate also provides bounded integers under the [`mod@bounded`] module which have additional
//! syntactical checks to guarantee that the deserialized values are in-bounds. It is also possible
//! to serialize and deserialize sequences of values by using the types provided in the [`prefix`]
//! module, which represent linear sequences of valeus with a length prefix.
//!
//! - Writing errors: Raised when there are issues while writing bytes.
//! - Reading errors: Raised when there are issues while reading bytes.
//! - Deserialization errors: Raised when the bytes being used to create a value are invalid for the data layout of such
//! value.
//! Check the [`Packable`] `impl` section for further information.
//!
//! # Replacing [`std::io`]
//! # Features
//!
//! We introduced the [`Packer`](packer::Packer) and [`Unpacker`](unpacker::Unpacker) taits to abstract away any IO
//! operation without relying on [`std::io`]. This has the additional benefit of allowing us to pack and unpack values
//! from different kinds of buffers.
//! ## `std`
//!
//! # Types that implement [`Packable`]
//! This feature implements [`Error`](std::error::Error) for all the error types provided by this
//! crate.
//!
//! The [`Packable`] trait is implemented for every integer type by encoding the value as an array of bytes in
//! little-endian order. Booleans are packed following Rust's data layout, meaning that `true` is packed as a `1` byte
//! and `false` as a `0` byte. However, boolean unpacking is less strict and unpacks any non-zero byte as `true`.
//! Additional implementations of [`Packable`] are provided for [`Vec<T>`](std::vec::Vec), `Box<[T]>`, `[T; N]` and
//! [`Option<T>`] if T implements [`Packable`].
//! ## `io`
//!
//! Check the [`Packable`] `impl` section for further information.
//! This feature provides the types [`IoPacker`](packer::IoPacker) and
//! [`IoUnpacker`](unpacker::IoUnpacker) which allow packing and unpacking from values whose types
//! implement [`Write`](std::io::Write) and [`Read`](std::io::Read) respectively.
//!
//! ## `usize`
//!
//! This featue implements [`Packable`] for [`usize`], [`isize`], [`Vec<T>`](std::vec::Vec),
//! `Box<[T]>`, [`String`](std::string::String) this is done serializing and deserializing pointer
//! sized integers as 64-bit integers. This feature will not work for targets with a pointer width
//! larger than 64.

#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
Expand Down
28 changes: 14 additions & 14 deletions packable/packable/src/packable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,35 +108,35 @@ use core::{convert::AsRef, fmt::Debug};
/// The derive implementation can be tweaked using `#[packable(...)]` attributes.
///
/// ## Tags for enums
///
/// A very common pattern when implementing `Packable` for enums consists in introducing a prefix
/// value to differentiate each variant of the enumeration when unpacking, this prefix value is
/// known as a `tag`. The type of the `tag` is specified with the `#[packable(tag_type = ...)]`
/// attribute and it can only be one of `u8`, `u16`, `u32` or `u64`. The `tag` value used for each
/// variant is specified with the `#[packable(tag = ...)]` attribute and can only contain integer
/// literal without any type prefixes (e.g. `42` is valid but `42u8` is not).
/// attribute and it can only be one of `[u8]`, `[u16]`, `[u32]` or `[u64]`. The `tag` value used
/// for each variant is specified with the `#[packable(tag = ...)]` attribute and can only contain
/// integer literal without any type prefixes (e.g. `42` is valid but `42u8` is not).
///
/// In the example above, the `tag` type is `u8`, the `Nothing` variant has a `tag` value of `0`
/// In the example above, the `tag` type is `[u8]`, the `Nothing` variant has a `tag` value of `0`
/// and the `Just` variant has a `tag` value of `1`. This means that the packed version of
/// `Maybe::Nothing` is `[0u8]` and the packed version of `Maybe::Just(7)` is `[1u8, 0u8, 0u8, 0u8,
/// 7u8]`.
/// `Maybe::Nothing` is `[0]` and the packed version of `Maybe::Just(7)` is `[1, 0, 0, 0, 7]`.
///
/// The `tag_type` and `tag` attributes are mandatory for enums unless the enum has a
/// `#[repr(...)]` attribute identifiern which case the `repr` type will be used as the `tag_type` and each
/// variant discriminant will be used as the `tag`. The `tag_type` and `tag` attributes take
/// precedence over the `repr` attribute.
/// `#[repr(...)]` attribute identifier, in which case the `repr` type will be used as the
/// `tag_type` and each variant discriminant will be used as the `tag`. The `tag_type` and `tag`
/// attributes take precedence over the `repr` attribute.
///
/// ## The `UnpackError` associated type
///
/// The derive macro provides the optional attribute and `#[packable(unpack_error = ...)]` to
/// specify the `UnpackError` associated type. The macro also provides sensible defaults for cases
/// when the attribute is not used.
///
/// For structs, the default `UnpackError` type is the `UnpackError` of any of the fields or
/// `core::convert::Infallible` in case the struct has no fields.
/// For structs, the default [`UnpackError`](Packable::UnpackError) type is the
/// [`UnpackError`](Packable::UnpackError) of any of the fields type or
/// [`Infallible`](core::convert::Infallible) in case the struct has no fields.
///
/// For enums, the default `UnpackError` type is `UnknownTagError<T>` where `T` is the type
/// specified according to the `tag_type` or `repr` attributes.
/// For enums, the default [`UnpackError`](Packable::UnpackError) type is
/// [`UnknownTagError<T>`](crate::error::UnknownTagError) where `T` is the type specified according
/// to the `tag_type` or `repr` attributes.
///
/// Following the example above, `Maybe::UnpackError` is `UnknownTagError<u8>` because no
/// `unpack_error` attribute was specified.
Expand Down