Skip to content

Commit

Permalink
add binary-sv2 no-serde codec docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Shourya742 committed Dec 7, 2024
1 parent ba796e0 commit 1c6d3fa
Show file tree
Hide file tree
Showing 9 changed files with 825 additions and 58 deletions.
70 changes: 62 additions & 8 deletions protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/decodable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,28 @@ use core::convert::TryFrom;
#[cfg(not(feature = "no_std"))]
use std::io::{Cursor, Read};

/// Implmented by all the decodable structure, it can be derived for every structure composed only
/// by primitives or other Decodable.
/// Custom deserialization of types from binary data.
///
/// Defines the process of reconstructing a type from a sequence of bytes. It handles both simple
/// and nested or complex data structures.
pub trait Decodable<'a>: Sized {
/// Defines the expected structure of a type based on binary data.
///
/// Returns a vector of [`FieldMarker`]s, each representing a component of the structure.
/// Useful for guiding the decoding process.
fn get_structure(data: &[u8]) -> Result<Vec<FieldMarker>, Error>;

/// Constructs the type from a vector of decoded fields.
///
/// After the data has been split into fields, this method combines those fields
/// back into the original type, handling nested structures or composite fields.
fn from_decoded_fields(data: Vec<DecodableField<'a>>) -> Result<Self, Error>;

/// Decodes the type from raw bytes.
///
/// Orchestrates the decoding process, calling `get_structure` to break down
/// the raw data, decoding each field, and then using `from_decoded_fields` to reassemble
/// the fields into the original type.
fn from_bytes(data: &'a mut [u8]) -> Result<Self, Error> {
let structure = Self::get_structure(data)?;
let mut fields = Vec::new();
Expand All @@ -34,6 +49,10 @@ pub trait Decodable<'a>: Sized {
Self::from_decoded_fields(fields)
}

/// Converts a readable input to self representation.
///
/// Reads data from an input which implements [`std::ioRead`] and constructs the original struct
/// out of it.
#[cfg(not(feature = "no_std"))]
fn from_reader(reader: &mut impl Read) -> Result<Self, Error> {
let mut data = Vec::new();
Expand All @@ -51,7 +70,10 @@ pub trait Decodable<'a>: Sized {
}
}

/// Passed to a decoder to define the structure of the data to be decoded
// Primitive data marker.
//
// Fundamental data types that can be passed to a decoder to define the structure of the type to be
// decoded in a standardized way.
#[derive(Debug, Clone, Copy)]
pub enum PrimitiveMarker {
U8,
Expand All @@ -71,17 +93,31 @@ pub enum PrimitiveMarker {
B016M,
}

/// Passed to a decoder to define the structure of the data to be decoded
/// Recursive enum representing data structure fields.
///
/// A `FieldMarker` can either be a primitive or a nested structure. The marker helps the decoder
/// understand the layout and type of each field in the data, guiding the decoding process.
#[derive(Debug, Clone)]
pub enum FieldMarker {
/// A primitive data type.
Primitive(PrimitiveMarker),

/// A structured type composed of multiple fields, allowing for nested data.
Struct(Vec<FieldMarker>),
}

/// Trait for retrieving the [`FieldMarker`] associated with a type.
///
/// Provides a standardized way to retrieve a `FieldMarker` for a type, allowing the protocol to
/// identify the structure and layout of data fields during decoding.
pub trait GetMarker {
/// Defines the structure of a type for decoding purposes, supporting both primitive and
/// structured types. It helps getting a marker for a type.
fn get_marker() -> FieldMarker;
}

/// Used to contrustuct primitives is returned by the decoder
// Represents a list of decode-able primitive data types.
//
#[derive(Debug)]
pub enum DecodablePrimitive<'a> {
U8(u8),
Expand All @@ -101,15 +137,24 @@ pub enum DecodablePrimitive<'a> {
B016M(B016M<'a>),
}

/// Used to contrustuct messages is returned by the decoder
/// Recursive enum representing a Decode-able field.
///
/// May be primitive or a nested struct.
///
/// Once the raw data is decoded, it is either classified as a primitive (e.g., integer, Boolean)
/// or a struct, which may itself contain multiple decoded fields. This type encapsulates that
/// distinction.
#[derive(Debug)]
pub enum DecodableField<'a> {
/// Primitive field.
Primitive(DecodablePrimitive<'a>),

/// Structured field, allowing for nested data structures.
Struct(Vec<DecodableField<'a>>),
}

impl SizeHint for PrimitiveMarker {
// PrimitiveMarker need introspection to return a size hint. This method is not implementeable
// PrimitiveMarker needs introspection to return a size hint. This method is not implementable.
fn size_hint(_data: &[u8], _offset: usize) -> Result<usize, Error> {
unimplemented!()
}
Expand Down Expand Up @@ -203,6 +248,9 @@ impl<'a> From<DecodableField<'a>> for Vec<DecodableField<'a>> {
}

impl PrimitiveMarker {
// Decodes a primitive value from a byte slice at the given offset, returning the corresponding
// `DecodablePrimitive`. The specific decoding logic depends on the type of the primitive (e.g.,
// `u8`, `u16`, etc.).
fn decode<'a>(&self, data: &'a mut [u8], offset: usize) -> DecodablePrimitive<'a> {
match self {
Self::U8 => DecodablePrimitive::U8(u8::from_bytes_unchecked(&mut data[offset..])),
Expand Down Expand Up @@ -235,9 +283,11 @@ impl PrimitiveMarker {
}
}

// Decodes a primitive value from a reader stream, returning the corresponding
// `DecodablePrimitive`. This is useful when reading data from a file or network socket,
// where the data is not immediately available as a slice but must be read incrementally.
#[allow(clippy::wrong_self_convention)]
#[cfg(not(feature = "no_std"))]
#[allow(clippy::wrong_self_convention)]
fn from_reader<'a>(&self, reader: &mut impl Read) -> Result<DecodablePrimitive<'a>, Error> {
match self {
Self::U8 => Ok(DecodablePrimitive::U8(u8::from_reader_(reader)?)),
Expand Down Expand Up @@ -288,6 +338,10 @@ impl<'a> GetSize for DecodablePrimitive<'a> {
}

impl FieldMarker {
// Implements the decoding functionality for a `FieldMarker`.
// Depending on whether the field is primitive or structured, this method decodes the
// corresponding data. If the field is a structure, it recursively decodes each nested field
// and returns the resulting `DecodableField`.
pub(crate) fn decode<'a>(&self, data: &'a mut [u8]) -> Result<DecodableField<'a>, Error> {
match self {
Self::Primitive(p) => Ok(DecodableField::Primitive(p.decode(data, 0))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,45 @@ use alloc::vec::Vec;
#[cfg(not(feature = "no_std"))]
use std::io::{Error as E, Write};

/// The `Encodable` trait defines the interface for encoding a type into bytes.
///
/// The trait provides methods for serializing an instance of a type into a byte
/// array or writing it directly into an output writer. The trait is flexible,
/// allowing various types, including primitives, structures, and collections,
/// to implement custom serialization logic.
///
/// The trait offers two key methods for encoding:
///
/// - The first, `to_bytes`, takes a mutable byte slice as a destination buffer. This method encodes
/// the object directly into the provided buffer, returning the number of bytes written or an
/// error if the encoding process fails.
/// - The second, `to_writer`, (only available when not compiling for `no-std`) accepts a writer as
/// a destination for the encoded bytes, allowing the serialized data to be written to any
/// implementor of the `Write` trait.
///
/// Implementing types can define custom encoding logic, and this trait is
/// especially useful when dealing with different data structures that need
/// to be serialized for transmission.
pub trait Encodable {
/// Encodes the object into the provided byte slice.
///
/// The method uses the destination buffer `dst` to write the serialized
/// bytes. It returns the number of bytes written on success or an `Error`
/// if encoding fails.
#[allow(clippy::wrong_self_convention)]
fn to_bytes(self, dst: &mut [u8]) -> Result<usize, Error>;

/// Write the encoded object into the provided writer.
///
/// Serializes the object and writes it directly
/// to the `dst` writer. It is only available in environments
/// where `std` is available. If the encoding fails, error is
/// returned.
#[cfg(not(feature = "no_std"))]
#[allow(clippy::wrong_self_convention)]
fn to_writer(self, dst: &mut impl Write) -> Result<(), E>;
}

//
impl<'a, T: Into<EncodableField<'a>>> Encodable for T {
#[allow(clippy::wrong_self_convention)]
fn to_bytes(self, dst: &mut [u8]) -> Result<usize, Error> {
Expand All @@ -34,27 +63,53 @@ impl<'a, T: Into<EncodableField<'a>>> Encodable for T {
}
}

/// The `EncodablePrimitive` enum defines primitive types that can be encoded.
///
/// The enum represents various data types, such a integers, bool, and byte array
/// that can be encoded into a byte representation. Each variant holds a specific
/// type, and encoding logic is provided through the `encode` method.
#[derive(Debug)]
pub enum EncodablePrimitive<'a> {
/// U8 Primitive, representing a byte
U8(u8),
/// Owned U8 Primitive, representing an owned byte
OwnedU8(u8),
/// U16 Primitive, representing a u16 type
U16(u16),
/// Bool Primitive, representing a bool type
Bool(bool),
/// U24 Primitive, representing a U24 type
U24(U24),
/// U256 Primitive, representing a U256 type
U256(U256<'a>),
/// ShortTxId Primitive, representing a ShortTxId type
ShortTxId(ShortTxId<'a>),
/// Signature Primitive, representing a Signature type
Signature(Signature<'a>),
/// U32 Primitive, representing a u32 type
U32(u32),
/// U32AsRef Primitive, representing a U32AsRef type
U32AsRef(U32AsRef<'a>),
/// F32 Primitive, representing a f32 type
F32(f32),
/// U64 Primitive, representing a u64 type
U64(u64),
/// B032 Primitive, representing a B032 type
B032(B032<'a>),
/// B0255 Primitive, representing a B0255 type
B0255(B0255<'a>),
/// B064K Primitive, representing a B064K type
B064K(B064K<'a>),
/// B016M Primitive, representing a B016M type
B016M(B016M<'a>),
}

impl<'a> EncodablePrimitive<'a> {
// Provides the encoding logic for each primitive type.
//
// The `encode` method takes the `EncodablePrimitive` variant and serializes it
// into the destination buffer `dst`. The method returns the number of bytes written
// . If the buffer is too small or encoding fails, it returns an error.
fn encode(&self, dst: &mut [u8]) -> Result<usize, Error> {
match self {
Self::U8(v) => v.to_slice(dst),
Expand All @@ -76,6 +131,11 @@ impl<'a> EncodablePrimitive<'a> {
}
}

// Write the encoded object into the provided writer.
//
// Serializes the object and writes it directly to the
// provided writer. It is only available in environments where `std`
// is available.
#[cfg(not(feature = "no_std"))]
pub fn write(&self, writer: &mut impl Write) -> Result<(), E> {
match self {
Expand All @@ -99,6 +159,7 @@ impl<'a> EncodablePrimitive<'a> {
}
}

// Provides the logic for calculating the size of the encodable field.
impl<'a> GetSize for EncodablePrimitive<'a> {
fn get_size(&self) -> usize {
match self {
Expand All @@ -122,13 +183,28 @@ impl<'a> GetSize for EncodablePrimitive<'a> {
}
}

/// The [`EncodableField`] enum defines encodable fields, which may be a primitive or struct.
///
/// Each [`EncodableField`] represents either a primitive value or a collection of values
/// (a struct). The encoding process for [`EncodableField`] supports nesting, allowing
/// for complex hierarchical data structures to be serialized.
#[derive(Debug)]
pub enum EncodableField<'a> {
/// Represents a primitive value
///
/// For the full supported list please see [`EncodablePrimitive`]
Primitive(EncodablePrimitive<'a>),
/// Represents a struct like field structure.
///
/// Note that this is a recursive enum type.
Struct(Vec<EncodableField<'a>>),
}

impl<'a> EncodableField<'a> {
/// The `encode` method serializes a field into the destination buffer `dst`, starting
/// at the provided `offset`. If the field is a structure, it recursively encodes
/// each contained field. If the buffer is too small or encoding fails, the method
/// returns an error.
pub fn encode(&self, dst: &mut [u8], mut offset: usize) -> Result<usize, Error> {
match (self, dst.len() >= offset) {
(Self::Primitive(p), true) => p.encode(&mut dst[offset..]),
Expand Down
Loading

0 comments on commit 1c6d3fa

Please sign in to comment.