diff --git a/protocols/v2/binary-sv2/binary-sv2/README.md b/protocols/v2/binary-sv2/binary-sv2/README.md index 7fac0315f..64df50747 100644 --- a/protocols/v2/binary-sv2/binary-sv2/README.md +++ b/protocols/v2/binary-sv2/binary-sv2/README.md @@ -1,3 +1,28 @@ -# binary_sv2 +# binary-sv2 -`binary_sv2` is a Rust `no_std` crate \ No newline at end of file +[![crates.io](https://img.shields.io/crates/v/binary-sv2.svg)](https://crates.io/crates/binary-sv2) +[![docs.rs](https://docs.rs/binary-sv2/badge.svg)](https://docs.rs/binary-sv2) +[![rustc+](https://img.shields.io/badge/rustc-1.75.0%2B-lightgrey.svg)](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html) +[![license](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/stratum-mining/stratum/blob/main/LICENSE.md) + +`binary-sv2` is a Rust `no-std` crate that helps encode and decode binary data into Stratum V2 messages — either through `serde` or custom trait-based setup. Allowing it to be used in environment(s) where std or/and serde are not available. + +## Key Capabilities + +- **Protocol-Specific Types**: Supports fixed and dynamically-sized SV2 types. +- **Optimized Memory Use**: Supports buffer pooling to enhance memory efficiency. + +## Features + +- **with_serde**: Enables `serde`-based encoding and decoding. +- **core**: Activates non-`serde` implementations via `binary_codec_sv2` and `derive_codec_sv2`.(default) +- **prop_test**: Adds property testing support. +- **with_buffer_pool**: Optimizes memory usage during encoding. + +## Usage + +To include this crate in your project, run: + +```sh +cargo add binary-sv2 +``` \ No newline at end of file diff --git a/protocols/v2/binary-sv2/binary-sv2/examples/encode_decode.rs b/protocols/v2/binary-sv2/binary-sv2/examples/encode_decode.rs new file mode 100644 index 000000000..f792b45f3 --- /dev/null +++ b/protocols/v2/binary-sv2/binary-sv2/examples/encode_decode.rs @@ -0,0 +1,118 @@ +pub use binary_codec_sv2::{self, Decodable as Deserialize, Encodable as Serialize, *}; +use core::convert::TryInto; +pub use derive_codec_sv2::{Decodable as Deserialize, Encodable as Serialize}; + +// The `Test` struct is expanded using the `Deserialize` and `Serialize` procedural macros. +// These macros provide the necessary methods for serializing and deserializing the struct. +// +// mod impl_parse_decodable_test { +// use super::binary_codec_sv2::{ +// decodable::DecodableField, decodable::FieldMarker, Decodable, Error, SizeHint, +// }; +// use super::*; +// impl<'decoder> Decodable<'decoder> for Test { +// fn get_structure(data: &[u8]) -> Result, Error> { +// let mut fields = Vec::new(); +// let mut offset = 0; +// let a: Vec = u32::get_structure(&data[offset..])?; +// offset += a.size_hint_(&data, offset)?; +// let a = a.try_into()?; +// fields.push(a); +// let b: Vec = u8::get_structure(&data[offset..])?; +// offset += b.size_hint_(&data, offset)?; +// let b = b.try_into()?; +// fields.push(b); +// let c: Vec = U24::get_structure(&data[offset..])?; +// offset += c.size_hint_(&data, offset)?; +// let c = c.try_into()?; +// fields.push(c); +// Ok(fields) +// } +// fn from_decoded_fields( +// mut data: Vec>, +// ) -> Result { +// Ok(Self { +// c: U24::from_decoded_fields( +// data.pop().ok_or(Error::NoDecodableFieldPassed)?.into(), +// )?, +// b: u8::from_decoded_fields( +// data.pop().ok_or(Error::NoDecodableFieldPassed)?.into(), +// )?, +// a: u32::from_decoded_fields( +// data.pop().ok_or(Error::NoDecodableFieldPassed)?.into(), +// )?, +// }) +// } +// } +// impl<'decoder> Test { +// pub fn into_static(self) -> Test { +// Test { +// a: self.a.clone(), +// b: self.b.clone(), +// c: self.c.clone(), +// } +// } +// } +// impl<'decoder> Test { +// pub fn as_static(&self) -> Test { +// Test { +// a: self.a.clone(), +// b: self.b.clone(), +// c: self.c.clone(), +// } +// } +// } +// } +// mod impl_parse_encodable_test { +// use super::binary_codec_sv2::{encodable::EncodableField, GetSize}; +// use super::Test; +// extern crate alloc; +// use alloc::vec::Vec; +// impl<'decoder> From for EncodableField<'decoder> { +// fn from(v: Test) -> Self { +// let mut fields: Vec = Vec::new(); +// let val = v.a; +// fields.push(val.into()); +// let val = v.b; +// fields.push(val.into()); +// let val = v.c; +// fields.push(val.into()); +// Self::Struct(fields) +// } +// } +// impl<'decoder> GetSize for Test { +// fn get_size(&self) -> usize { +// let mut size = 0; +// size += self.a.get_size(); +// size += self.b.get_size(); +// size += self.c.get_size(); +// size +// } +// } +// } +// + +#[derive(Clone, Deserialize, Serialize, PartialEq, Debug)] +struct Test { + a: u32, + b: u8, + c: U24, +} + +fn main() { + let expected = Test { + a: 456, + b: 9, + c: 67_u32.try_into().unwrap(), + }; + + // `to_bytes` serves as the entry point to the `binary_sv2` crate. It acts as a serializer that + // converts the struct into bytes. + let mut bytes = to_bytes(expected.clone()).unwrap(); + + // `from_bytes` is a deserializer that interprets the bytes and reconstructs the original + // struct. + let deserialized: Test = from_bytes(&mut bytes[..]).unwrap(); + + assert_eq!(deserialized, expected); +} diff --git a/protocols/v2/binary-sv2/binary-sv2/src/lib.rs b/protocols/v2/binary-sv2/binary-sv2/src/lib.rs index 85fc6b31b..2210148e0 100644 --- a/protocols/v2/binary-sv2/binary-sv2/src/lib.rs +++ b/protocols/v2/binary-sv2/binary-sv2/src/lib.rs @@ -1,5 +1,22 @@ -// TODO unify errors from serde_sv2 and no-serde-sv2 - +//! Mediates between two implementations of the `binary_sv2` protocol, +//! enabling encoding and decoding through `serde` or custom traits. +//! +//! # Overview +//! +//! Depending on the feature flags enabled, this crate will re-export implementations of the +//! `Deserialize` and `Serialize` traits either from the `serde` library or from a custom, +//! `serde`-free implementation provided by `binary_codec_sv2` and `derive_codec_sv2`. This allows +//! for flexible integration of SV2 protocol types and binary serialization for environments that +//! may not support `serde`. +//! +//! ## Features +//! - **with_serde**: Enables `serde`-based serialization and deserialization for SV2 types, using +//! `serde` and `serde_sv2`. +//! - **core**: Enables the custom `binary_codec_sv2` and `derive_codec_sv2` implementations, which +//! provide `Deserialize` and `Serialize` traits without the need for `serde`. +//! - **prop_test**: Adds support for property testing for protocol types. +//! - **with_buffer_pool**: Enables support for buffer pooling to optimize memory usage during +//! serialization and deserialization. #![no_std] #[macro_use] @@ -19,10 +36,12 @@ pub use binary_codec_sv2::{self, Decodable as Deserialize, Encodable as Serializ #[cfg(not(feature = "with_serde"))] pub use derive_codec_sv2::{Decodable as Deserialize, Encodable as Serialize}; +/// Does nothing and will be removed during refactor pub fn clone_message(_: T) -> T { todo!() } +/// Converts a value implementing the `Into` trait into a custom `U256` type. pub fn u256_from_int>(value: V) -> U256<'static> { // initialize u256 as a bytes vec of len 24 let mut u256 = vec![0_u8; 24]; diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/README.md b/protocols/v2/binary-sv2/no-serde-sv2/codec/README.md index f338a94db..9189991c1 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/README.md +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/README.md @@ -1,3 +1,44 @@ # binary_codec_sv2 -`binary_codec_sv2` is a Rust `no_std` crate \ No newline at end of file +[![crates.io](https://img.shields.io/crates/v/binary_codec_sv2.svg)](https://crates.io/crates/binary_codec_sv2) +[![docs.rs](https://docs.rs/binary_codec_sv2/badge.svg)](https://docs.rs/binary_codec_sv2) +[![rustc+](https://img.shields.io/badge/rustc-1.75.0%2B-lightgrey.svg)](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html) +[![license](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/stratum-mining/stratum/blob/main/LICENSE.md) +[![codecov](https://codecov.io/gh/stratum-mining/stratum/branch/main/graph/badge.svg?flag=binary_codec_sv2-coverage)](https://codecov.io/gh/stratum-mining/stratum) + +`binary_codec_sv2` is a `no_std` Rust crate that helps serialize and de-serialize binary data into and from Stratum V2 types. + +## Key Features + +- **Comprehensive Encoding and Decoding**: Provides traits (`Encodable`, `Decodable`) for converting between Rust and SV2 data types/structures. +- **Support for Complex Data Structures**: Handles primitives, nested structures, and protocol-specific types like `U24`, `U256`,`Str0255` and rest. +- **Error Handling**: Robust mechanisms for managing encoding/decoding failures, including size mismatches and invalid data. +- **Cross-Language Compatibility**: Utilities like `CVec` and `CError` ensure smooth integration with other programming languages. +- **`no_std` Compatibility**: Fully supports constrained environments without the Rust standard library. + +## Sv2 Type Mapping + +The crate supports the following mappings between Rust and SV2 types + +| Rust Type | Sv2 Type | +|-------------|----------------| +| `bool` | `BOOL` | +| `u8` | `U8` | +| `u16` | `U16` | +| `U24` | `U24` | +| `u32` | `U32` | +| `u64` | `U64` | +| `f32` | `F32` | +| `Str0255` | `STRO_255` | +| `Signature` | `SIGNATURE` | +| `[u8]` | `BYTES` | +| `Seq0255` | `SEQ0_255[T]` | +| `Seq064K` | `SEQ0_64K[T]` | + +## Installation + +Add `binary_codec_sv2` to your project by running: + +```sh +cargo add binary_codec_sv2 +``` diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/decodable.rs b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/decodable.rs index 1ec7037f7..84f99cc2b 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/decodable.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/decodable.rs @@ -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, 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>) -> Result; + /// 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 { let structure = Self::get_structure(data)?; let mut fields = Vec::new(); @@ -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 { let mut data = Vec::new(); @@ -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, @@ -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), } + +/// 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), @@ -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>), } 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 { unimplemented!() } @@ -203,6 +248,9 @@ impl<'a> From> for Vec> { } 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..])), @@ -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, Error> { match self { Self::U8 => Ok(DecodablePrimitive::U8(u8::from_reader_(reader)?)), @@ -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, Error> { match self { Self::Primitive(p) => Ok(DecodableField::Primitive(p.decode(data, 0))), diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/encodable.rs b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/encodable.rs index 70be94ecb..69c0cc550 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/encodable.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/encodable.rs @@ -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; + /// 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>> Encodable for T { #[allow(clippy::wrong_self_convention)] fn to_bytes(self, dst: &mut [u8]) -> Result { @@ -34,27 +63,53 @@ impl<'a, T: Into>> 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 { match self { Self::U8(v) => v.to_slice(dst), @@ -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 { @@ -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 { @@ -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>), } 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 { match (self, dst.len() >= offset) { (Self::Primitive(p), true) => p.encode(&mut dst[offset..]), diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/mod.rs b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/mod.rs index a26ae8e72..32a3e0847 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/mod.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/mod.rs @@ -1,9 +1,63 @@ -// At lower level I generally prefer to work with slices as more efficient than Read/Write streams -// eg: Read for & [u8] is implemented with memcpy but here is more than enough to just get a -// pointer to the original data - -// ANche se enum decode sarebbe faclie da implementare non viene fatto dato che ogni messaggio puo -// essere derivato dal suo numero! +// Provides traits and utilities for encoding and decoding data at a low level, +// prioritizing efficiency and memory safety by operating directly on slices rather than +// relying on `Read` or `Write` streams. +// +// ## Overview +// +// Optimized for performance, this module directly manipulates byte slices, avoiding +// the overhead of stream-based I/O. It uses memory-efficient techniques like obtaining +// pointers to original data instead of copying it. Enums are avoided for decoding, as +// each message type can be identified by its numeric identifier, streamlining the process. +// +// ### Key Components +// +// - **Traits**: Defines core traits (`SizeHint`, `GetSize`, `Fixed`, `Variable`) that establish a +// consistent interface for encoding and decoding operations. +// - **Buffer Management**: With the `with_buffer_pool` feature enabled, the `Slice` type from +// `buffer_sv2` is included, supporting memory pooling and efficient slice handling for +// high-performance buffer management scenarios. +// +// ### Traits Overview +// +// - **`SizeHint`**: Estimates the size of a decodable type, useful for variable-length data where +// the size must be determined dynamically. +// - **`GetSize`**: Provides the exact size of an encodable type in bytes, crucial for buffer +// allocation. +// - **`Fixed`**: Marks types with a compile-time constant size, simplifying encoding and decoding. +// - **`Variable`**: For types with dynamic sizes, manages size variability and calculates inner +// sizes. +// +// ## Build Options +// +// - **`no_std` Compatibility**: This module can be compiled without the standard library for +// constrained environments. Some methods and traits are conditionally available when `std` is +// included. +// - **`with_buffer_pool`**: When enabled, includes the `Slice` type for managing pooled memory +// slices, improving memory handling and efficiency in high-performance scenarios. +// +// ## Detailed Trait Descriptions +// +// ### `SizeHint` +// Defines methods to calculate the size of encoded data for types with variable sizes. +// - **`size_hint`**: Returns the total size of the encoded data for raw data and an offset. +// - **`size_hint_`**: Returns the size for a specific instance, offering flexibility. +// +// ### `GetSize` +// Provides a `get_size` method that returns the exact size in bytes of an encodable type. +// +// ### `Fixed` +// For types with a fixed size, this trait defines a constant `SIZE`, simplifying work with +// fixed-size types. +// +// ### `Variable` +// Types with variable sizes implement this trait, providing constants (`HEADER_SIZE`, `MAX_SIZE`) +// and methods for size management and inner size calculation. +// +// ## Summary +// +// This module supports efficient, low-level encoding and decoding by operating directly on slices, +// avoiding excess data copying. It offers capabilities for both fixed and variable-sized data, +// making it versatile for a wide range of encoding tasks. use crate::Error; pub mod decodable; pub mod encodable; @@ -13,14 +67,28 @@ use buffer_sv2::Slice; use alloc::vec::Vec; -/// Return the encoded byte size or a `Decodable` +/// The `SizeHint` trait provides a mechanism to return the encoded bytes size of a decodable type. +/// +/// It defines two methods for retrieving the size of an encoded message: +/// +/// +/// These methods are crucial in decoding scenarios where the full size of the message +/// is not immediately available, helping to determine how many bytes need to be read. pub trait SizeHint { + /// `size_hint` is a static method that takes the raw data and an offset and returns the total + /// size of the encoded message. This is particularly useful for types where the encoded size + /// may vary based on the contents of the data, and we need to calculate how much space is + /// required for decoding. fn size_hint(data: &[u8], offset: usize) -> Result; + /// `size_hint_` is an instance method that performs the same function but allows the size to be + /// be determined with respect to the current instance of the type. fn size_hint_(&self, data: &[u8], offset: usize) -> Result; } -/// Return the encoded byte size of an `Encodable` comprehensive of the header, if any +/// [`GetSize`] is a trait defining a single function that calculates the total size of an +/// encodable type. pub trait GetSize { + /// `get_size` returns total size of the type in bytes. fn get_size(&self) -> usize; } @@ -30,12 +98,20 @@ impl GetSize for Slice { self.len() } } - -/// Implemented by all the primitives with a fixed size +/// [`Fixed`] is a trait is defining a single element representing a size of a constant. +/// +/// This is useful for primitives with a constant fixed size +/// +/// Types implementing this trait must define the constant `SIZE`, representing the +/// fixed number of bytes needed to encode or decode the type. This trait is used for +/// types that have a know size at compile time , such as integers, fixed-size arrays, etc. pub trait Fixed { + ///the constant `SIZE`, represents the fixed number of bytes needed to encode or decode the + /// type. const SIZE: usize; } +// Not used and will be removed during refactoring pub trait Variable { const HEADER_SIZE: usize; //const ELEMENT_SIZE: usize; @@ -43,12 +119,12 @@ pub trait Variable { fn inner_size(&self) -> usize; - // That could be [u8; Self::HEADER_SIZE] but rust do not allow it + // Retrieves the header as a byte vector. This header typically contains information + // about the size or type of the data that follows. fn get_header(&self) -> Vec; } impl SizeHint for T { - /// Total size of the encoded data type compreensive of the header when present fn size_hint(_data: &[u8], _offset: usize) -> Result { Ok(Self::SIZE) } diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/copy_data_types.rs b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/copy_data_types.rs index 690f719fd..d9ca0a4ab 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/copy_data_types.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/copy_data_types.rs @@ -1,4 +1,32 @@ -//! Copy data types +// Provides implementations for encoding and decoding copy data types as required by the SV2 +// protocol. Facilitates byte-level serialization and deserialization, particularly for types +// without dynamically-sized data. +// +// ## Traits and Implementations +// +// ### `Fixed` +// The `Fixed` trait is implemented for various data types to specify a fixed size for each, +// enabling consistent memory allocation during serialization. The `SIZE` constant for each type +// defines its byte size, with implementations provided for `bool`, unsigned integers (e.g., `u8`, +// `u16`, `u32`, `u64`), and custom types like `U24`. +// +// ### `Sv2DataType` +// The `Sv2DataType` trait is implemented for these data types, providing methods for encoding and +// decoding operations such as `from_bytes_unchecked`, `from_vec_`, `from_reader_` (if `std` is +// available), and `to_slice_unchecked`. The methods use little-endian byte order for consistency +// across platforms. +// +// ## Special Types +// +// ### `U24` +// A custom 24-bit unsigned integer, represented as a `U24` struct, handles 3-byte data often used +// in SV2 protocols for memory-efficient encoding. Provides conversion methods to and from `u32`, +// with `TryFrom` ensuring values stay within the 24-bit range (0 to 16,777,215). +// +// ## Macros +// The `impl_sv2_for_unsigned` macro streamlines the implementation of the `Sv2DataType` trait for +// unsigned integer types, ensuring little-endian byte ordering for serialization and handling both +// in-memory buffers and `std::io::Read`/`Write` interfaces when `std` is available. use crate::{codec::Fixed, datatypes::Sv2DataType, Error}; use alloc::vec::Vec; @@ -13,15 +41,6 @@ impl Fixed for bool { const SIZE: usize = 1; } -// Boolean value. Encoded as an unsigned 1-bit integer, -// True = 1, False = 0 with 7 additional padding bits in -// the high positions. -// x -// Recipients MUST NOT interpret bits outside of the -// least significant bit. Senders MAY set bits outside of -// the least significant bit to any value without any -// impact on meaning. This allows future use of other -// bits as flag bits. impl<'a> Sv2DataType<'a> for bool { fn from_bytes_unchecked(data: &'a mut [u8]) -> Self { match data @@ -87,6 +106,11 @@ impl Fixed for u64 { const SIZE: usize = 8; } +/// Macro to implement the `Sv2DataType` trait for unsigned integer types. +/// +/// Simplifies encoding and decoding for various unsigned integer types, making them +/// compatible with the SV2 protocol. Each implementation uses the little-endian byte order for +/// serialization and deserialization, ensuring consistency across platforms. macro_rules! impl_sv2_for_unsigned { ($a:ty) => { impl<'a> Sv2DataType<'a> for $a { @@ -132,14 +156,15 @@ impl_sv2_for_unsigned!(u16); impl_sv2_for_unsigned!(u32); impl_sv2_for_unsigned!(u64); -// Impl f32 as a primitives - impl Fixed for f32 { const SIZE: usize = 4; } impl_sv2_for_unsigned!(f32); +/// Represents a 24-bit unsigned integer (`U24`), supporting SV2 serialization and deserialization. +/// Only first 3 bytes of a u32 is considered to get the SV2 value, and rest are ignored (in little +/// endian). #[repr(C)] #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct U24(pub(crate) u32); diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/mod.rs b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/mod.rs index 8ab1bc924..468e82d50 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/mod.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/mod.rs @@ -1,3 +1,33 @@ +// Provides implementations for encoding and decoding data types in the SV2 protocol, +// supporting both fixed-size and dynamically-sized types. Defines the `Sv2DataType` trait, +// which standardizes serialization and deserialization across various types, including +// those with custom requirements like byte padding and dynamic sizing. +// +// ## Structure and Contents +// +// ### `Sv2DataType` Trait +// The `Sv2DataType` trait offers methods to: +// - **Deserialize**: Convert byte slices or reader sources into Rust types. +// - **Serialize**: Encode Rust types into byte slices or write them to I/O streams. +// +// Supports both **checked** and **unchecked** variants for serialization and deserialization. +// Checked functions validate data lengths, while unchecked versions assume size correctness for +// optimized performance. +// +// ### Modules +// - **`copy_data_types`**: Defines fixed-size types directly copied into or from byte slices, such +// as `U24` (24-bit unsigned integer). +// - **`non_copy_data_types`**: Manages dynamically-sized types, like sequences, public keys, and +// strings, requiring size handling logic for SV2 compatibility. +// +// ### Re-exports +// Re-exports common data types used in SV2 serialization, such as `PubKey`, `Signature`, `Seq0255`, +// and others, simplifying protocol data handling with concrete implementations of `Sv2DataType`. +// +// The `Sv2DataType` trait and its implementations enable seamless conversion between in-memory +// representations and serialized forms, ensuring efficient protocol communication and +// interoperability. + use crate::{ codec::{GetSize, SizeHint}, Error, @@ -17,21 +47,40 @@ use core::convert::TryInto; #[cfg(not(feature = "no_std"))] use std::io::{Error as E, Read, Write}; +/// `Sv2DataType` is a trait that defines methods for encoding and decoding Stratum V2 data. +/// It is used for serializing and deserializing both fixed-size and dynamically-sized types. +/// +/// Key Responsibilities: +/// - Serialization: Converting data from in-memory representations to byte slices or streams. +/// - Deserialization: Converting byte slices or streams back into the in-memory representation of +/// the data. +/// +/// This trait includes functions for both checked and unchecked conversions, providing flexibility +/// in situations where error handling can be safely ignored. pub trait Sv2DataType<'a>: Sized + SizeHint + GetSize + TryInto { + /// Creates an instance of the type from a mutable byte slice, checking for size constraints. + /// + /// This function verifies that the provided byte slice has the correct size according to the + /// type's size hint. fn from_bytes_(data: &'a mut [u8]) -> Result { Self::size_hint(data, 0)?; Ok(Self::from_bytes_unchecked(data)) } + /// Constructs an instance from a mutable byte slice without verifying size constraints. fn from_bytes_unchecked(data: &'a mut [u8]) -> Self; + /// Constructs an instance from a vector, checking for the correct size. fn from_vec_(data: Vec) -> Result; + /// Constructs an instance from a vector without validating its size. fn from_vec_unchecked(data: Vec) -> Self; + // Constructs an instance from a reader source, checking for size constraints. #[cfg(not(feature = "no_std"))] fn from_reader_(reader: &mut impl Read) -> Result; + /// Serializes the instance to a mutable slice, checking the destination size. fn to_slice(&'a self, dst: &mut [u8]) -> Result { if dst.len() >= self.get_size() { self.to_slice_unchecked(dst); @@ -41,8 +90,10 @@ pub trait Sv2DataType<'a>: Sized + SizeHint + GetSize + TryInto { } } + /// Serializes the instance to a mutable slice without checking the destination size. fn to_slice_unchecked(&'a self, dst: &mut [u8]); + // Serializes the instance to a writer destination, checking for I/O errors. #[cfg(not(feature = "no_std"))] fn to_writer_(&self, writer: &mut impl Write) -> Result<(), E>; } diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/inner.rs b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/inner.rs index e0f30bf38..d3deeae09 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/inner.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/inner.rs @@ -1,3 +1,39 @@ +// Provides a flexible container for managing either owned or mutable references to byte arrays. +// +// # Overview +// Defines the `Inner` enum to manage both mutable references to byte slices and owned vectors +// (`Vec`). Accommodates both fixed-size and variable-size data using const generics, offering +// control over size and header length constraints. +// +// # `Inner` Enum +// The `Inner` enum has two variants for data management: +// - `Ref(&'a mut [u8])`: A mutable reference to a byte slice, allowing in-place data modification. +// - `Owned(Vec)`: An owned byte vector, providing full control over data and supporting move +// semantics. +// +// ## Const Parameters +// Configured using const generics for the following constraints: +// - `ISFIXED`: Indicates whether the data has a fixed size. +// - `SIZE`: Specifies the size when `ISFIXED` is true. +// - `HEADERSIZE`: Defines the size of the header, useful for variable-size data with a prefix +// length. +// - `MAXSIZE`: Limits the maximum allowable size of the data. +// +// # Usage +// `Inner` offers several methods for data manipulation, including: +// - `to_vec()`: Returns a `Vec`, cloning the slice or owned data. +// - `inner_as_ref()` and `inner_as_mut()`: Provide immutable or mutable access to the data. +// - `expected_length(data: &[u8])`: Computes the expected length, validating it against +// constraints. +// - `get_header()`: Returns the data's header based on `HEADERSIZE`. +// +// # Implementations +// The `Inner` enum implements `PartialEq`, `Eq`, `GetSize`, `SizeHint`, and `Sv2DataType` traits, +// enabling buffer size calculations, reading, and writing to byte slices. +// +// # Error Handling +// Methods return `Error` types when data exceeds size limits or deviates from the configuration, +// ensuring compliance with defined constraints. use super::IntoOwned; use crate::{ codec::{GetSize, SizeHint}, @@ -10,6 +46,21 @@ use core::convert::{TryFrom, TryInto}; #[cfg(not(feature = "no_std"))] use std::io::{Error as E, Read, Write}; +// The `Inner` enum represents a flexible container for managing both reference to mutable +// slices and owned bytes arrays (`Vec`). This design allows the container to either own +// its data or simply reference existing mutable data. It uses const generics to differentiate +// between fixed-size and variable-size data, as well as to specify key size-related parameters. +// +// It has two variants: +// - `Ref(&'a mut [u8])`: A mutable reference to an external byte slice. +// - `Owned (Vec)`: A vector that owns its data, enabling dynamic ownership. +// +// The const parameters that govern the behavior of this enum are: +// - `ISFIXED`: A boolean indicating whether the data has a fixed size. +// - `SIZE`: The size of the data if `ISFIXED` is true. +// - `HEADERSIZE`: The size of the header, which is used for types that require a prefix to +// describe the content's length. +// - `MAXSIZE`: The maximum allowable size for the data. #[repr(C)] #[derive(Debug)] pub enum Inner< @@ -25,18 +76,24 @@ pub enum Inner< // TODO add test for that and implement it also with serde!!!! impl<'a, const SIZE: usize> Inner<'a, true, SIZE, 0, 0> { + // Converts the inner data to a vector, either by cloning the referenced slice or + // returning a clone of the owned vector. pub fn to_vec(&self) -> Vec { match self { Inner::Ref(ref_) => ref_.to_vec(), Inner::Owned(v) => v.clone(), } } + // Returns an immutable reference to the inner data, whether it's a reference or + // an owned vector. pub fn inner_as_ref(&self) -> &[u8] { match self { Inner::Ref(ref_) => ref_, Inner::Owned(v) => v, } } + // Provides a mutable reference to the inner data, allowing modification if the + // data is being referenced. pub fn inner_as_mut(&mut self) -> &mut [u8] { match self { Inner::Ref(ref_) => ref_, @@ -48,12 +105,17 @@ impl<'a, const SIZE: usize> Inner<'a, true, SIZE, 0, 0> { impl<'a, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> Inner<'a, false, SIZE, HEADERSIZE, MAXSIZE> { + // Similar to the fixed-size variant, this method converts the inner data into a vector. + // The data is either cloned from the referenced slice or returned as a clone of the + // owned vector. pub fn to_vec(&self) -> Vec { match self { Inner::Ref(ref_) => ref_[..].to_vec(), Inner::Owned(v) => v[..].to_vec(), } } + // Returns an immutable reference to the inner data for variable-size types, either + // referencing a slice or an owned vector. pub fn inner_as_ref(&self) -> &[u8] { match self { Inner::Ref(ref_) => &ref_[..], @@ -65,6 +127,8 @@ impl<'a, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> PartialEq for Inner<'a, ISFIXED, SIZE, HEADERSIZE, MAXSIZE> { + // Provides equality comparison between two `Inner` instances by checking the equality + // of their data, regardless of whether they are references or owned vectors. fn eq(&self, other: &Self) -> bool { match (self, other) { (Inner::Ref(b), Inner::Owned(a)) => *b == &a[..], @@ -83,6 +147,10 @@ impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> Inner<'a, ISFIXED, SIZE, HEADERSIZE, MAXSIZE> { + // Calculates the expected length of the data based on the type's parameters (fixed-size + // or variable-size). It checks if the length conforms to the specified constraints like + // `SIZE`, `MAXSIZE`, and `HEADERSIZE`, returning the length or an error if the data + // exceeds the limits. fn expected_length(data: &[u8]) -> Result { let expected_length = match ISFIXED { true => Self::expected_length_fixed(), @@ -95,10 +163,14 @@ impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const } } + // For fixed-size data, the expected length is always `SIZE`. fn expected_length_fixed() -> usize { SIZE } + // For variable-size data, this method calculates the size based on the header. + // The header describes the length of the data, and this method ensures the data + // is correctly sized relative to the header information. fn expected_length_variable(data: &[u8]) -> Result { if data.len() >= HEADERSIZE { let size = match HEADERSIZE { @@ -116,6 +188,8 @@ impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const } } + // Similar to the above but operates on a reader instead of a byte slice, reading + // the header from the input and calculating the expected length of the data to be read. #[cfg(not(feature = "no_std"))] fn expected_length_for_reader(mut reader: impl Read) -> Result { if ISFIXED { @@ -140,6 +214,8 @@ impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const } } + /// Returns the length of the data, either from the reference or the owned vector, + /// or the fixed size if `ISFIXED` is true. pub fn len(&self) -> usize { match (self, ISFIXED) { (Inner::Ref(data), false) => data.len(), @@ -148,6 +224,8 @@ impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const } } + // Retrieves the header as a byte vector. If `HEADERSIZE` is zero, an empty vector is + // returned. Otherwise, the header is constructed from the length of the data. fn get_header(&self) -> Vec { if HEADERSIZE == 0 { Vec::new() diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/mod.rs b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/mod.rs index 40c47a7ba..af9399515 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/mod.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/mod.rs @@ -1,3 +1,35 @@ +// Provides a flexible, low-level interface for representing fixed-size and variable-size byte +// arrays, simplifying serialization and deserialization of cryptographic and protocol data. +// +// The core component is the [`Inner`] type, a wrapper for managing both fixed and variable-length +// data slices or owned values. It offers aliases for commonly used data types like 32-byte hashes +// (`U256`), public keys (`PubKey`), cryptographic signatures (`Signature`), and dynamically-sized +// arrays (`B0255`, `B064K`). + +// # Features +// - **Fixed-size Aliases**: Types like [`U32AsRef`], [`U256`], [`ShortTxId`], [`PubKey`], and +// [`Signature`] represent specific byte sizes, often used in cryptographic contexts or protocol +// identifiers. +// - **Variable-size Aliases**: Types like [`B032`], [`B0255`], [`Str0255`], [`B064K`], and +// [`B016M`] handle data with bounded sizes, providing flexibility for dynamic data. +// - **Traits and Conversions**: Implements traits like `From`, `TryFrom`, and [`IntoOwned`] for +// seamless transformations between owned and reference-based values. +// - **Property Testing** (optional, requires the `prop_test` feature): Supports generating +// arbitrary test data for property-based testing. + +// # Type Aliases +// - **[`U32AsRef`]**: 4-byte representation for small identifiers or integer values. +// - **[`U256`]**: 32-byte cryptographic hash (e.g., SHA-256 or protocol IDs). +// - **[`ShortTxId`]**: 6-byte transaction ID. +// - **[`PubKey`]**: 32-byte public key (e.g., Ed25519). +// - **[`Signature`]**: 64-byte cryptographic signature. +// - **[`B032`], [`B0255`], [`Str0255`]**: Variable-size representations for optional fields or +// protocol data. + +// # Feature Flags +// - **`prop_test`**: Enables property-based testing with the `quickcheck` crate. When enabled, +// types like `U256` and `B016M` gain methods to generate arbitrary test data for testing +// serialization and deserialization. #[cfg(feature = "prop_test")] use quickcheck::{Arbitrary, Gen}; @@ -15,15 +47,35 @@ trait IntoOwned { pub use inner::Inner; pub use seq_inner::{Seq0255, Seq064K, Sv2Option}; +/// Type alias for a 4-byte slice or owned data represented using the `Inner` +/// type with fixed-size configuration. pub type U32AsRef<'a> = Inner<'a, true, 4, 0, 0>; +/// Type alias for a 32-byte slice or owned data (commonly used for cryptographic +/// hashes or IDs) represented using the `Inner` type with fixed-size configuration. pub type U256<'a> = Inner<'a, true, 32, 0, 0>; +/// Type alias for a 6-byte transaction ID (TxId) represented using the `Inner` +/// type with fixed-size configuration. pub type ShortTxId<'a> = Inner<'a, true, 6, 0, 0>; +/// Type alias for a 32-byte public key represented using the `Inner` type +/// with fixed-size configuration. pub type PubKey<'a> = Inner<'a, true, 32, 0, 0>; +/// Type alias for a 64-byte cryptographic signature represented using the +/// `Inner` type with fixed-size configuration. pub type Signature<'a> = Inner<'a, true, 64, 0, 0>; +/// Type alias for a variable-sized byte array with a maximum size of 32 bytes, +/// represented using the `Inner` type with a 1-byte header. pub type B032<'a> = Inner<'a, false, 1, 1, 32>; +/// Type alias for a variable-sized byte array with a maximum size of 255 bytes, +/// represented using the `Inner` type with a 1-byte header. pub type B0255<'a> = Inner<'a, false, 1, 1, 255>; +/// Type alias for a variable-sized string with a maximum size of 255 bytes, +/// represented using the `Inner` type with a 1-byte header. pub type Str0255<'a> = Inner<'a, false, 1, 1, 255>; +/// Type alias for a variable-sized byte array with a maximum size of 64 KB, +/// represented using the `Inner` type with a 2-byte header. pub type B064K<'a> = Inner<'a, false, 1, 2, { u16::MAX as usize }>; +/// Type alias for a variable-sized byte array with a maximum size of ~16 MB, +/// represented using the `Inner` type with a 3-byte header. pub type B016M<'a> = Inner<'a, false, 1, 3, { 2_usize.pow(24) - 1 }>; impl<'decoder> From<[u8; 32]> for U256<'decoder> { @@ -53,6 +105,7 @@ impl<'a> B016M<'a> { use core::convert::{TryFrom, TryInto}; +// Attempts to convert a `String` into a `Str0255<'a>`. impl<'a> TryFrom for Str0255<'a> { type Error = crate::Error; @@ -61,7 +114,10 @@ impl<'a> TryFrom for Str0255<'a> { } } +/// Represents a reference to a 32-bit unsigned integer (`u32`), +/// providing methods for convenient conversions. impl<'a> U32AsRef<'a> { + /// Returns the `u32` value represented by this reference. pub fn as_u32(&self) -> u32 { let inner = self.inner_as_ref(); u32::from_le_bytes([inner[0], inner[1], inner[2], inner[3]]) diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/seq_inner.rs b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/seq_inner.rs index 9da799ba9..af7471019 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/seq_inner.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/datatypes/non_copy_data_types/seq_inner.rs @@ -1,3 +1,54 @@ +// # Sequence and Optional Data Structures +// +// Provides specialized implementations of sequences and optional data types, primarily +// designed to handle serialized data with fixed size constraints. These structures are particularly +// suited for encoding and decoding variable-length and optional data fields within serialized +// formats. +// +// ## Provided Types +// +// ### `Seq0255` +// - Represents a sequence of up to 255 elements. +// - Includes utility methods such as: +// - `to_vec()`: Converts each element into its byte vector representation. +// - `inner_as_ref()`: Provides references to the inner data for each element. +// - `new()`: Creates a `Seq0255` instance, enforcing the maximum length constraint. +// - Implements the `Decodable` trait for seamless deserialization, and `GetSize` to calculate the +// encoded size, ensuring compatibility with various serialization formats. +// +// ### `Seq064K` +// - Represents a sequence of up to 65535 elements. +// - Similar to `Seq0255`, it provides: +// - `to_vec()` and `inner_as_ref()` methods to convert or reference each element. +// - `new()` enforces the maximum size limit, preventing excess memory usage. +// - Like `Seq0255`, `Seq064K` is `Decodable` and implements `GetSize`, making it versatile for +// serialization scenarios. +// +// ### `Sv2Option` +// - Represents an optional data type, encoding a single or absent element. +// - Provides `to_option()` to convert to a standard `Option>`. +// - `new()` and `into_inner()` enable flexible conversions between `Option` and `Sv2Option`. +// +// ## Utility Macros +// +// - `impl_codec_for_sequence!`: Implements the `Decodable` trait for a sequence type, allowing for +// a custom deserialization process that interprets field markers. +// - `impl_into_encodable_field_for_seq!`: Implements conversions to `EncodableField` for a +// sequence, adapting the sequence for inclusion in serialized structures. +// +// ## Notes on Serialization +// +// Types are designed to interoperate with the `serde-sv2` framework, using lifetimes +// (`'a`) for compatibility with external lifetimes and ensuring the types can be converted into +// various serialized forms with or without `serde` support. +// +// ## Build Options +// +// - `prop_test`: Enables property-based testing compatibility by implementing `TryFrom` for `Vec` +// conversions. +// - `no_std`: Allows the module to be used in `no_std` environments by disabling `std::io::Read` +// dependencies. + use crate::{ codec::{ decodable::{Decodable, DecodableField, FieldMarker, GetMarker, PrimitiveMarker}, @@ -13,9 +64,11 @@ use core::marker::PhantomData; impl<'a, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> Seq0255<'a, super::inner::Inner<'a, false, SIZE, HEADERSIZE, MAXSIZE>> { + /// Converts the inner types to owned vector, and collects. pub fn to_vec(&self) -> Vec> { self.0.iter().map(|x| x.to_vec()).collect() } + /// Converts the inner types to shared reference, and collects. pub fn inner_as_ref(&self) -> Vec<&[u8]> { self.0.iter().map(|x| x.inner_as_ref()).collect() } @@ -23,9 +76,12 @@ impl<'a, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> // TODO add test for that and implement it also with serde!!!! impl<'a, const SIZE: usize> Seq0255<'a, super::inner::Inner<'a, true, SIZE, 0, 0>> { + /// Converts the inner types to owned vector, and collects. pub fn to_vec(&self) -> Vec> { self.0.iter().map(|x| x.to_vec()).collect() } + + /// Converts the inner types to shared reference, and collects. pub fn inner_as_ref(&self) -> Vec<&[u8]> { self.0.iter().map(|x| x.inner_as_ref()).collect() } @@ -34,9 +90,12 @@ impl<'a, const SIZE: usize> Seq0255<'a, super::inner::Inner<'a, true, SIZE, 0, 0 impl<'a, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> Seq064K<'a, super::inner::Inner<'a, false, SIZE, HEADERSIZE, MAXSIZE>> { + /// Converts the inner types to owned vector, and collects. pub fn to_vec(&self) -> Vec> { self.0.iter().map(|x| x.to_vec()).collect() } + + /// Converts the inner types to shared reference, and collects. pub fn inner_as_ref(&self) -> Vec<&[u8]> { self.0.iter().map(|x| x.inner_as_ref()).collect() } @@ -44,9 +103,12 @@ impl<'a, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> // TODO add test for that and implement it also with serde!!!! impl<'a, const SIZE: usize> Seq064K<'a, super::inner::Inner<'a, true, SIZE, 0, 0>> { + /// Converts the inner types to owned vector, and collects. pub fn to_vec(&self) -> Vec> { self.0.iter().map(|x| x.to_vec()).collect() } + + /// Converts the inner types to shared reference, and collects. pub fn inner_as_ref(&self) -> Vec<&[u8]> { self.0.iter().map(|x| x.inner_as_ref()).collect() } @@ -55,7 +117,9 @@ impl<'a, const SIZE: usize> Seq064K<'a, super::inner::Inner<'a, true, SIZE, 0, 0 #[cfg(not(feature = "no_std"))] use std::io::Read; -/// The liftime is here only for type compatibility with serde-sv2 +/// [`Seq0255`] represents a sequence with a maximum length of 255 elements. +/// This structure uses a generic type `T` and a lifetime parameter `'a` +/// to ensure compatibility with `serde-sv2`. #[repr(C)] #[derive(Debug, Clone, Eq, PartialEq)] pub struct Seq0255<'a, T>(pub Vec, PhantomData<&'a T>); @@ -63,7 +127,7 @@ pub struct Seq0255<'a, T>(pub Vec, PhantomData<&'a T>); impl<'a, T: 'a> Seq0255<'a, T> { const HEADERSIZE: usize = 1; - /// Return the len of the inner vector + // Determines the expected length of the sequence by examining the first byte of `data`. fn expected_len(data: &[u8]) -> Result { if data.len() >= Self::HEADERSIZE { Ok(data[0] as usize) @@ -72,6 +136,7 @@ impl<'a, T: 'a> Seq0255<'a, T> { } } + /// Creates a new `Seq0255` instance with the given inner vector. pub fn new(inner: Vec) -> Result { if inner.len() <= 255 { Ok(Self(inner, PhantomData)) @@ -80,12 +145,14 @@ impl<'a, T: 'a> Seq0255<'a, T> { } } + /// Consumes the `Seq0255` and returns the inner vector of elements. pub fn into_inner(self) -> Vec { self.0 } } impl<'a, T: GetSize> GetSize for Seq0255<'a, T> { + // Calculates the total size of the sequence in bytes. fn get_size(&self) -> usize { let mut size = Self::HEADERSIZE; for with_size in &self.0 { @@ -95,14 +162,16 @@ impl<'a, T: GetSize> GetSize for Seq0255<'a, T> { } } -/// The liftime is here only for type compatibility with serde-sv2 +/// [`Seq064K`] represents a sequence with a maximum length of 65535 elements. +/// This structure uses a generic type `T` and a lifetime parameter `'a` +/// to ensure compatibility with `serde-sv2`. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Seq064K<'a, T>(pub(crate) Vec, PhantomData<&'a T>); impl<'a, T: 'a> Seq064K<'a, T> { const HEADERSIZE: usize = 2; - /// Return the len of the inner vector + // Determines the expected length of the sequence by examining the first two bytes of `data`. fn expected_len(data: &[u8]) -> Result { if data.len() >= Self::HEADERSIZE { Ok(u16::from_le_bytes([data[0], data[1]]) as usize) @@ -111,6 +180,7 @@ impl<'a, T: 'a> Seq064K<'a, T> { } } + /// Creates a new `Seq064K` instance with the given inner vector. pub fn new(inner: Vec) -> Result { if inner.len() <= 65535 { Ok(Self(inner, PhantomData)) @@ -119,6 +189,7 @@ impl<'a, T: 'a> Seq064K<'a, T> { } } + /// Consumes the `Seq064K` and returns the inner vector of elements. pub fn into_inner(self) -> Vec { self.0 } @@ -134,6 +205,8 @@ impl<'a, T: GetSize> GetSize for Seq064K<'a, T> { } } +/// Macro to implement encoding and decoding traits for sequence types (`Seq0255`, `Seq064K`, and +/// `Sv2Option`). macro_rules! impl_codec_for_sequence { ($a:ty) => { impl<'a, T: 'a + Sv2DataType<'a> + GetMarker + GetSize + Decodable<'a>> Decodable<'a> @@ -210,10 +283,14 @@ macro_rules! impl_codec_for_sequence { }; } +// Implementations for encoding/decoding impl_codec_for_sequence!(Seq0255<'a, T>); impl_codec_for_sequence!(Seq064K<'a, T>); impl_codec_for_sequence!(Sv2Option<'a, T>); +/// The `impl_into_encodable_field_for_seq` macro provides implementations of the `From` trait +/// to convert `Seq0255`, `Seq064K`, and `Sv2Option` types into `EncodableField`, making these +/// sequence types compatible with encoding. macro_rules! impl_into_encodable_field_for_seq { ($a:ty) => { impl<'a> From> for EncodableField<'a> { @@ -316,12 +393,14 @@ impl<'a, T> From> for Seq064K<'a, T> { } impl<'a, T: Fixed> Seq0255<'a, T> { + /// converts the lifetime to static pub fn into_static(self) -> Seq0255<'static, T> { // Safe unwrap cause the initial value is a valid Seq0255 Seq0255::new(self.0).unwrap() } } impl<'a, T: Fixed> Sv2Option<'a, T> { + /// converts the lifetime to static pub fn into_static(self) -> Sv2Option<'static, T> { Sv2Option::new(self.into_inner()) } @@ -330,6 +409,7 @@ impl<'a, T: Fixed> Sv2Option<'a, T> { impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> Seq0255<'a, Inner<'a, ISFIXED, SIZE, HEADERSIZE, MAXSIZE>> { + /// converts the lifetime to static pub fn into_static( self, ) -> Seq0255<'static, Inner<'static, ISFIXED, SIZE, HEADERSIZE, MAXSIZE>> { @@ -343,6 +423,7 @@ impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> Sv2Option<'a, Inner<'a, ISFIXED, SIZE, HEADERSIZE, MAXSIZE>> { + /// converts the lifetime to static pub fn into_static( self, ) -> Sv2Option<'static, Inner<'static, ISFIXED, SIZE, HEADERSIZE, MAXSIZE>> { @@ -353,6 +434,7 @@ impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const } impl<'a, T: Fixed> Seq064K<'a, T> { + /// converts the lifetime to static pub fn into_static(self) -> Seq064K<'static, T> { // Safe unwrap cause the initial value is a valid Seq064K Seq064K::new(self.0).unwrap() @@ -362,6 +444,7 @@ impl<'a, T: Fixed> Seq064K<'a, T> { impl<'a, const ISFIXED: bool, const SIZE: usize, const HEADERSIZE: usize, const MAXSIZE: usize> Seq064K<'a, Inner<'a, ISFIXED, SIZE, HEADERSIZE, MAXSIZE>> { + /// converts the lifetime to static pub fn into_static( self, ) -> Seq064K<'static, Inner<'static, ISFIXED, SIZE, HEADERSIZE, MAXSIZE>> { @@ -379,6 +462,7 @@ pub struct Sv2Option<'a, T>(pub Vec, PhantomData<&'a T>); // TODO add test for that and implement it also with serde!!!! impl<'a, const SIZE: usize> Sv2Option<'a, super::inner::Inner<'a, true, SIZE, 0, 0>> { + /// Gets the owned first element of the sequence, if present pub fn to_option(&self) -> Option> { let v: Vec> = self.0.iter().map(|x| x.to_vec()).collect(); match v.len() { @@ -388,6 +472,7 @@ impl<'a, const SIZE: usize> Sv2Option<'a, super::inner::Inner<'a, true, SIZE, 0, _ => unreachable!(), } } + /// Gets the reference to first element of the sequence, if present pub fn inner_as_ref(&self) -> Option<&[u8]> { let v: Vec<&[u8]> = self.0.iter().map(|x| x.inner_as_ref()).collect(); match v.len() { @@ -415,6 +500,7 @@ impl<'a, T: 'a> Sv2Option<'a, T> { } } + /// Initializes a new option type pub fn new(inner: Option) -> Self { match inner { Some(x) => Self(vec![x], PhantomData), @@ -422,6 +508,7 @@ impl<'a, T: 'a> Sv2Option<'a, T> { } } + /// Gets the inner value of Sv2Option pub fn into_inner(mut self) -> Option { let len = self.0.len(); match len { diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/lib.rs b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/lib.rs index cbca39977..6e2250a95 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/src/lib.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/src/lib.rs @@ -1,16 +1,29 @@ +//! Defines types, encodings, and conversions between Serde and SV2 protocols, +//! providing abstractions for encoding, decoding, and error handling of SV2 data types. +//! +//! # Overview +//! +//! Enables conversion between various Rust types and SV2-specific data formats for efficient +//! network communication. Provides utilities to encode and decode data types according to the SV2 +//! specifications. +//! +//! ## Type Mappings +//! The following table illustrates how standard Rust types or serde data model map to their SV2 +//! counterparts: +//! //! ```txt //! SERDE <-> Sv2 //! bool <-> BOOL //! u8 <-> U8 //! u16 <-> U16 //! U24 <-> U24 -//! u32 <-> u32 -//! f32 <-> f32 // not in the spec but used -//! u64 <-> u64 // not in the spec but used +//! u32 <-> U32 +//! f32 <-> F32 // Not in the spec, but used +//! u64 <-> U64 //! U256 <-> U256 //! Str0255 <-> STRO_255 //! Signature<-> SIGNATURE -//! B032 <-> B0_32 // not in the spec but used +//! B032 <-> B0_32 //! B0255 <-> B0_255 //! B064K <-> B0_64K //! B016M <-> B0_16M @@ -19,6 +32,49 @@ //! Seq0255 <-> SEQ0_255[T] //! Seq064K <-> SEQ0_64K[T] //! ``` +//! +//! # Encoding & Decoding +//! +//! Enables conversion between various Rust types and SV2-specific data formats for efficient +//! network communication. Provides utilities to encode and decode data types according to the SV2 +//! specifications. +//! +//! - **to_bytes**: Encodes an SV2 data type into a byte vector. +//! - **to_writer**: Encodes an SV2 data type into a byte slice. +//! - **from_bytes**: Decodes an SV2-encoded byte slice into the specified data type. +//! +//! # Error Handling +//! +//! Defines an `Error` enum for handling failure conditions during encoding, decoding, and data +//! manipulation. Common errors include: +//! - Out-of-bounds accesses +//! - Size mismatches during encoding/decoding +//! - Invalid data representations, such as non-boolean values interpreted as booleans. +//! +//! # Cross-Language Interoperability +//! +//! To support foreign function interface (FFI) use cases, the module includes `CError` and `CVec` +//! types that represent SV2 data and errors in a format suitable for cross-language compatibility. +//! +//! # Build Options +//! +//! Supports optional features like `no_std` for environments without standard library support. +//! Error types are conditionally compiled to work with or without `std`. +//! +//! ## Conditional Compilation +//! - With the `no_std` feature enabled, I/O-related errors use a simplified `IoError` +//! representation. +//! - Standard I/O errors (`std::io::Error`) are used when `no_std` is disabled. +//! +//! # FFI Interoperability +//! +//! Provides utilities for FFI (Foreign Function Interface) to enable data passing between Rust and +//! other languages. Includes: +//! - `CVec`: Represents a byte vector for safe passing between C and Rust. +//! - `CError`: A C-compatible error type. +//! - `CVec2`: Manages collections of `CVec` objects across FFI boundaries. +//! +//! Facilitates integration of SV2 functionality into cross-language projects. #![cfg_attr(feature = "no_std", no_std)] @@ -40,6 +96,7 @@ pub use crate::codec::{ use alloc::vec::Vec; +/// Converts the provided SV2 data type to a byte vector based on the SV2 encoding format. #[allow(clippy::wrong_self_convention)] pub fn to_bytes(src: T) -> Result, Error> { let mut result = vec![0_u8; src.get_size()]; @@ -47,21 +104,106 @@ pub fn to_bytes(src: T) -> Result, Error> { Ok(result) } +/// Encodes the SV2 data type to the provided byte slice. #[allow(clippy::wrong_self_convention)] pub fn to_writer(src: T, dst: &mut [u8]) -> Result<(), Error> { src.to_bytes(dst)?; Ok(()) } +/// Decodes an SV2-encoded byte slice into the specified data type. pub fn from_bytes<'a, T: Decodable<'a>>(data: &'a mut [u8]) -> Result { T::from_bytes(data) } +/// Provides an interface and implementation details for decoding complex data structures +/// from raw bytes or I/O streams. Handles deserialization of nested and primitive data +/// structures through traits, enums, and helper functions for managing the decoding process. +/// +/// # Overview +/// The [`Decodable`] trait serves as the core component, offering methods to define a type's +/// structure, decode raw byte data, and construct instances from decoded fields. It supports both +/// in-memory byte slices and I/O streams for flexibility across deserialization use cases. +/// +/// # Key Concepts and Types +/// - **[`Decodable`] Trait**: Defines methods to decode types from byte data, process individual +/// fields, and construct complete types. +/// - **[`FieldMarker`] and `PrimitiveMarker`**: Enums that represent data types or structures, +/// guiding the decoding process by defining field structures and types. +/// - **[`DecodableField`] and `DecodablePrimitive`**: Represent decoded fields as either primitives +/// or nested structures, forming the building blocks for complex data types. +/// +/// # Error Handling +/// Custom error types manage issues during decoding, such as insufficient data or unsupported +/// types. Errors are surfaced through `Result` types to ensure reliability in data parsing tasks. +/// +/// # `no_std` Support +/// Compatible with `no_std` environments through conditional compilation. Omits I/O-dependent +/// methods like `from_reader` when `no_std` is enabled, ensuring lightweight builds for constrained +/// environments. pub mod decodable { pub use crate::codec::decodable::{Decodable, DecodableField, FieldMarker}; //pub use crate::codec::decodable::PrimitiveMarker; } +/// Provides an encoding framework for serializing various data types into bytes. +/// +/// The [`Encodable`] trait is the core of this framework, enabling types to define +/// how they serialize data into bytes. This is essential for transmitting data +/// between components or systems in a consistent, byte-oriented format. +/// +/// ## Overview +/// +/// Supports a wide variety of data types, including basic types (e.g., integers, +/// booleans, and byte arrays) and complex structures. Each type’s encoding logic is +/// encapsulated in enums like [`EncodablePrimitive`] and [`EncodableField`], enabling +/// structured and hierarchical data serialization. +/// +/// ### Key Types +/// +/// - **[`Encodable`]**: Defines methods for converting an object into a byte array or writing it +/// directly to an output stream. It supports both primitive types and complex structures. +/// - **[`EncodablePrimitive`]**: Represents basic types that can be serialized directly. Includes +/// data types like integers, booleans, and byte arrays. +/// - **[`EncodableField`]**: Extends [`EncodablePrimitive`] to support structured and nested data, +/// enabling recursive encoding of complex structures. +/// +/// ### `no_std` Compatibility +/// +/// When compiled with the `no_std` feature enabled, this module omits the `to_writer` method +/// to support environments without the standard library. Only buffer-based encoding +/// (`to_bytes`) is available in this mode. +/// +/// ## Error Handling +/// +/// Errors during encoding are handled through the [`Error`] type. Common failure scenarios include +/// buffer overflows and type-specific serialization errors. Each encoding method returns an +/// appropriate error if encoding fails, supporting comprehensive error management. +/// +/// ## Trait Details +/// +/// ### [`Encodable`] +/// - **`to_bytes`**: Encodes the instance into a byte slice, returning the number of bytes written +/// or an error if encoding fails. +/// - **`to_writer`** (requires `std`): Encodes the instance into any [`Write`] implementor, such as +/// a file or network stream. +/// +/// ### Additional Enums and Methods +/// +/// Includes utility types and methods for calculating sizes, encoding hierarchical data, +/// and supporting both owned and reference-based data variants. +/// +/// - **[`EncodablePrimitive`]**: Handles encoding logic for primitive types, addressing +/// serialization requirements specific to each type. +/// - **[`EncodableField`]**: Extends encoding to support composite types and structured data, +/// enabling recursive encoding of nested structures. +/// +/// ## Summary +/// +/// Designed for flexibility and extensibility, this module supports a wide range of data +/// serialization needs through customizable encoding strategies. Implementing the +/// [`Encodable`] trait for custom types ensures efficient and consistent data serialization +/// across applications. pub mod encodable { pub use crate::codec::encodable::{Encodable, EncodableField, EncodablePrimitive}; } @@ -69,39 +211,89 @@ pub mod encodable { #[macro_use] extern crate alloc; +/// Error types used within the protocol library to indicate various failure conditions. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Error { + /// Indicates an attempt to read beyond a valid range. OutOfBound, + + /// Raised when a non-binary value is interpreted as a boolean. NotABool(u8), - /// -> (expected size, actual size) + + /// Occurs when an unexpected size mismatch arises during a write operation, specifying + /// expected and actual sizes. WriteError(usize, usize), + + /// Signifies an overflow condition where a `u32` exceeds the maximum allowable `u24` value. U24TooBig(u32), + + /// Reports a size mismatch for a signature, such as when it does not match the expected size. InvalidSignatureSize(usize), + + /// Raised when a `u256` value is invalid, typically due to size discrepancies. InvalidU256(usize), + + /// Indicates an invalid `u24` representation. InvalidU24(u32), + + /// Error indicating that a byte array exceeds the maximum allowed size for `B0255`. InvalidB0255Size(usize), + + /// Error indicating that a byte array exceeds the maximum allowed size for `B064K`. InvalidB064KSize(usize), + + /// Error indicating that a byte array exceeds the maximum allowed size for `B016M`. InvalidB016MSize(usize), + + /// Raised when a sequence size exceeds `0255`. InvalidSeq0255Size(usize), - /// Error when trying to encode a non-primitive data type + + /// Error when trying to encode a non-primitive data type. NonPrimitiveTypeCannotBeEncoded, + + /// Generic conversion error related to primitive types. PrimitiveConversionError, + + /// Error occurring during decoding due to conversion issues. DecodableConversionError, + + /// Error triggered when a decoder is used without initialization. UnInitializedDecoder, + #[cfg(not(feature = "no_std"))] + /// Represents I/O-related errors, compatible with `no_std` mode where specific error types may + /// vary. IoError(E), + #[cfg(feature = "no_std")] + /// Represents I/O-related errors, compatible with `no_std` mode. IoError, + + /// Raised when an unexpected mismatch occurs during read operations, specifying expected and + /// actual read sizes. ReadError(usize, usize), + + /// Used as a marker error for fields that should remain void or empty. VoidFieldMarker, - /// Error when `Inner` type value exceeds max size. - /// (ISFIXED, SIZE, HEADERSIZE, MAXSIZE, bad value vec, bad value length) + + /// Signifies a value overflow based on protocol restrictions, containing details about + /// fixed/variable size, maximum size allowed, and the offending value details. ValueExceedsMaxSize(bool, usize, usize, usize, Vec, usize), - /// Error when sequence value (`Seq0255`, `Seq064K`) exceeds max size + + /// Triggered when a sequence type (`Seq0255`, `Seq064K`) exceeds its maximum allowable size. SeqExceedsMaxSize, + + /// Raised when no valid decodable field is provided during decoding. NoDecodableFieldPassed, + + /// Error for protocol-specific invalid values. ValueIsNotAValidProtocol(u8), + + /// Raised when an unsupported or unknown message type is encountered. UnknownMessageType(u8), + + /// Indicates a protocol constraint violation where `Sv2Option` unexpectedly contains multiple + /// elements. Sv2OptionHaveMoreThenOneElement(u8), } @@ -115,41 +307,91 @@ impl From for Error { } } -/// FFI-safe Error +/// `CError` is a foreign function interface (FFI)-compatible version of the `Error` enum to +/// facilitate cross-language compatibility. #[repr(C)] #[derive(Debug)] pub enum CError { + /// Indicates an attempt to read beyond a valid range. OutOfBound, + + /// Raised when a non-binary value is interpreted as a boolean. NotABool(u8), - /// -> (expected size, actual size) + + /// Occurs when an unexpected size mismatch arises during a write operation, specifying + /// expected and actual sizes. WriteError(usize, usize), + + /// Signifies an overflow condition where a `u32` exceeds the maximum allowable `u24` value. U24TooBig(u32), + + /// Reports a size mismatch for a signature, such as when it does not match the expected size. InvalidSignatureSize(usize), + + /// Raised when a `u256` value is invalid, typically due to size discrepancies. InvalidU256(usize), + + /// Indicates an invalid `u24` representation. InvalidU24(u32), + + /// Error indicating that a byte array exceeds the maximum allowed size for `B0255`. InvalidB0255Size(usize), + + /// Error indicating that a byte array exceeds the maximum allowed size for `B064K`. InvalidB064KSize(usize), + + /// Error indicating that a byte array exceeds the maximum allowed size for `B016M`. InvalidB016MSize(usize), + + /// Raised when a sequence size exceeds `0255`. InvalidSeq0255Size(usize), - /// Error when trying to encode a non-primitive data type + + /// Error when trying to encode a non-primitive data type. NonPrimitiveTypeCannotBeEncoded, + + /// Generic conversion error related to primitive types. PrimitiveConversionError, + + /// Error occurring during decoding due to conversion issues. DecodableConversionError, + + /// Error triggered when a decoder is used without initialization. UnInitializedDecoder, + #[cfg(not(feature = "no_std"))] + /// Represents I/O-related errors, compatible with `no_std` mode where specific error types may + /// vary. IoError(E), + #[cfg(feature = "no_std")] + /// Represents I/O-related errors, compatible with `no_std` mode. IoError, + + /// Raised when an unexpected mismatch occurs during read operations, specifying expected and + /// actual read sizes. ReadError(usize, usize), + + /// Used as a marker error for fields that should remain void or empty. VoidFieldMarker, - /// Error when `Inner` type value exceeds max size. - /// (ISFIXED, SIZE, HEADERSIZE, MAXSIZE, bad value vec, bad value length) + + /// Signifies a value overflow based on protocol restrictions, containing details about + /// fixed/variable size, maximum size allowed, and the offending value details. ValueExceedsMaxSize(bool, usize, usize, usize, CVec, usize), - /// Error when sequence value (`Seq0255`, `Seq064K`) exceeds max size + + /// Triggered when a sequence type (`Seq0255`, `Seq064K`) exceeds its maximum allowable size. SeqExceedsMaxSize, + + /// Raised when no valid decodable field is provided during decoding. NoDecodableFieldPassed, + + /// Error for protocol-specific invalid values. ValueIsNotAValidProtocol(u8), + + /// Raised when an unsupported or unknown message type is encountered. UnknownMessageType(u8), + + /// Indicates a protocol constraint violation where `Sv2Option` unexpectedly contains multiple + /// elements. Sv2OptionHaveMoreThenOneElement(u8), } @@ -246,6 +488,7 @@ impl<'a> From for EncodableField<'a> { } } +/// A struct to facilitate transferring a `Vec` across FFI boundaries. #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct CVec { @@ -255,16 +498,22 @@ pub struct CVec { } impl CVec { + /// Returns a mutable slice of the contained data. + /// + /// # Safety + /// + /// The caller must ensure that the data pointed to by `self.data` + /// remains valid for the duration of the returned slice. pub fn as_mut_slice(&mut self) -> &mut [u8] { unsafe { core::slice::from_raw_parts_mut(self.data, self.len) } } - /// Used when we need to fill a buffer allocated in rust from C. + /// Fills a buffer allocated in Rust from C. /// /// # Safety /// - /// This function construct a CVec without taking ownership of the pointed buffer so if the - /// owner drop them the CVec will point to garbage. + /// Constructs a `CVec` without taking ownership of the pointed buffer. If the owner drops the + /// buffer, the `CVec` will point to invalid memory. #[allow(clippy::wrong_self_convention)] pub fn as_shared_buffer(v: &mut [u8]) -> Self { let (data, len) = (v.as_mut_ptr(), v.len()); @@ -296,9 +545,11 @@ impl From<&[u8]> for CVec { } } -/// Given a C allocated buffer return a rust allocated CVec +/// Creates a `CVec` from a buffer that was allocated in C. /// /// # Safety +/// The caller must ensure that the buffer is valid and that +/// the data length does not exceed the allocated size. #[no_mangle] pub unsafe extern "C" fn cvec_from_buffer(data: *const u8, len: usize) -> CVec { let input = core::slice::from_raw_parts(data, len); @@ -319,6 +570,7 @@ pub unsafe extern "C" fn cvec_from_buffer(data: *const u8, len: usize) -> CVec { } } +/// A struct to manage a collection of `CVec` objects across FFI boundaries. #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct CVec2 { @@ -328,6 +580,7 @@ pub struct CVec2 { } impl CVec2 { + /// `as_mut_slice`: helps to get a mutable slice pub fn as_mut_slice(&mut self) -> &mut [CVec] { unsafe { core::slice::from_raw_parts_mut(self.data, self.len) } } @@ -338,10 +591,12 @@ impl From for Vec { } } +/// Frees the underlying memory of a `CVec`. pub fn free_vec(buf: &mut CVec) { let _: Vec = unsafe { Vec::from_raw_parts(buf.data, buf.len, buf.capacity) }; } +/// Frees the underlying memory of a `CVec2` and all its elements. pub fn free_vec_2(buf: &mut CVec2) { let vs: Vec = unsafe { Vec::from_raw_parts(buf.data, buf.len, buf.capacity) }; for mut s in vs { @@ -389,7 +644,10 @@ impl<'a, const A: bool, const B: usize, const C: usize, const D: usize> } } +/// Initializes an empty `CVec2`. +/// /// # Safety +/// The caller is responsible for freeing the `CVec2` when it is no longer needed. #[no_mangle] pub unsafe extern "C" fn init_cvec2() -> CVec2 { let mut buffer = Vec::::new(); @@ -407,9 +665,11 @@ pub unsafe extern "C" fn init_cvec2() -> CVec2 { } } -/// The caller is reponsible for NOT adding duplicate cvecs to the cvec2 structure, -/// as this can lead to double free errors when the message is dropped. +/// Adds a `CVec` to a `CVec2`. +/// /// # Safety +/// The caller must ensure no duplicate `CVec`s are added, as duplicates may +/// lead to double-free errors when the message is dropped. #[no_mangle] pub unsafe extern "C" fn cvec2_push(cvec2: &mut CVec2, cvec: CVec) { let mut buffer: Vec = Vec::from_raw_parts(cvec2.data, cvec2.len, cvec2.capacity); @@ -459,9 +719,12 @@ impl<'a, T: Into> From> for CVec2 { } } +/// Exported FFI functions for interoperability with C code for u24 #[no_mangle] pub extern "C" fn _c_export_u24(_a: U24) {} +/// Exported FFI functions for interoperability with C code for CVec #[no_mangle] pub extern "C" fn _c_export_cvec(_a: CVec) {} +/// Exported FFI functions for interoperability with C code for CVec2 #[no_mangle] pub extern "C" fn _c_export_cvec2(_a: CVec2) {} diff --git a/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/README.md b/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/README.md index 01af2599e..7ed3d2a14 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/README.md +++ b/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/README.md @@ -1,3 +1,23 @@ # derive_codec_sv2 -`derive_codec_sv2` is a Rust `no_std` crate \ No newline at end of file +[![crates.io](https://img.shields.io/crates/v/derive-codec-sv2.svg)](https://crates.io/crates/derive-codec-sv2) +[![docs.rs](https://docs.rs/derive-codec-sv2/badge.svg)](https://docs.rs/derive-codec-sv2) +[![rustc+](https://img.shields.io/badge/rustc-1.75.0%2B-lightgrey.svg)](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html) +[![license](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/stratum-mining/stratum/blob/main/LICENSE.md) +[![codecov](https://codecov.io/gh/stratum-mining/stratum/branch/main/graph/badge.svg?flag=binary_codec_sv2-coverage)](https://codecov.io/gh/stratum-mining/stratum) + +`derive-codec-sv2` is a no-std Rust crate offering procedural macros for automating serialization and deserialization of structs used within the Sv2 (Stratum V2) protocol. This crate provides `Encodable` and `Decodable` macros to streamline binary data handling, especially useful for protocol-level implementations where efficient encoding and decoding are essential. + +## Key Capabilities + +- **Automatic Encoding and Decoding**: Derives methods for converting structs to and from binary format, reducing boilerplate code for data structures used in Sv2. +- **Attribute-Based Configuration**: Supports `#[already_sized]` attribute for marking fixed-size structs, enabling optimizations in binary handling. +- **Flexible Field Parsing**: Allows parsing of fields with lifetimes, generics, and static references, enhancing compatibility with various protocol requirements. +- **Custom Size Calculation**: Provides field-specific size calculation through the derived `GetSize` trait, helpful for dynamic protocol message framing. + +## Usage + +To include this crate in your project, run: + +```sh +cargo add derive-codec-sv2 diff --git a/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/src/lib.rs b/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/src/lib.rs index 1cd3176f3..124e774da 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/src/lib.rs +++ b/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/src/lib.rs @@ -1,3 +1,48 @@ +//! # Procedural Macros for Automatic Serialization and Deserialization +//! +//! Provides procedural macros for deriving serialization and deserialization +//! traits on structs used in binary protocol communication. The macros `Encodable` and `Decodable` +//! generate implementations of encoding and decoding behaviors, making it simpler to work with +//! binary data by automatically handling field parsing, sizing, and transformation. +//! +//! ## Overview +//! +//! These macros parse struct definitions to produce code that supports efficient and type-safe +//! serialization and deserialization. Each field within a struct is processed based on its type and +//! associated generics, allowing for custom encoding schemes and alignment with protocol +//! requirements. Additionally, the macros enable flexible handling of lifetimes and static +//! references to ensure compatibility across different use cases. +//! +//! ## Available Macros +//! +//! - **`Encodable`**: Automatically implements encoding logic, converting a struct's fields into a +//! binary format. +//! - **Attributes**: `#[already_sized]` (optional) to specify that a struct's size is fixed at +//! compile-time. +//! - **Generated Traits**: `EncodableField` (field-by-field encoding) and `GetSize` (size +//! calculation). +//! +//! - **`Decodable`**: Automatically implements decoding logic, allowing a struct to be +//! reconstructed from binary data. +//! - **Generated Methods**: `get_structure` (defines field structure) and `from_decoded_fields` +//! (builds the struct from decoded fields). +//! +//! ## Internal Structure +//! +//! ### `is_already_sized` +//! Checks if the `#[already_sized]` attribute is present on the struct, allowing certain +//! optimizations in generated code for fixed-size structs. +//! +//! ### `get_struct_properties` +//! Parses and captures a struct’s name, generics, and field data, enabling custom encoding and +//! decoding functionality. +//! +//! ### Custom Implementations +//! The `Encodable` macro generates an `EncodableField` implementation by serializing each field, +//! while `Decodable` constructs the struct from binary data. Both macros provide support for +//! structs with or without lifetimes, ensuring versatility in applications that require efficient, +//! protocol-level data handling. + #![no_std] extern crate alloc; @@ -11,6 +56,37 @@ use alloc::{ use core::iter::FromIterator; use proc_macro::{Group, TokenStream, TokenTree}; +// Checks if a `TokenStream` contains a group with a bracket delimiter (`[]`), +// and further examines if the group has an identifier called `already_sized`. +// +// Iterates through the `TokenStream`, searching for a group of tokens +// that is delimited by square brackets (`[]`). Once a group is found, it looks inside +// the group for an identifier named `already_sized`. If such an identifier is found, +// the function returns `true`. Otherwise, it returns `false`. +// +// # Example +// +// ```ignore +// use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; +// use quote::quote; +// +// let input: TokenStream = quote! { +// [already_sized] +// }; +// +// // Call the function to check if `already_sized` is present inside the bracket group. +// assert_eq!(is_already_sized(input), true); +// +// // Now let's try another TokenStream that doesn't contain the `already_sized` identifier. +// let input_without_already_sized: TokenStream = quote! { +// [some_other_ident] +// }; +// +// assert_eq!(is_already_sized(input_without_already_sized), false); +// ``` +// +// In this example, the function successfully detects the presence of the `already_sized` +// identifier when it's wrapped inside brackets, and returns `true` accordingly. fn is_already_sized(item: TokenStream) -> bool { let stream = item.into_iter(); @@ -29,6 +105,43 @@ fn is_already_sized(item: TokenStream) -> bool { } false } + +// Filters out attributes from a `TokenStream` that are prefixed with `#`. +// +// Removes all Rust attributes (e.g., `#[derive(...)]`, `#[cfg(...)]`) +// from the provided `TokenStream`, leaving behind only the core structure and +// its fields. This is useful in procedural macros when you want to manipulate +// the underlying data without the attributes. +// +// # Example +// +// ```ignore +// use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; +// use quote::quote; +// +// let input: TokenStream = quote! { +// #[derive(Debug, Clone)] +// pub struct MyStruct { +// pub field1: i32, +// #[cfg(feature = "extra")] +// pub field2: String, +// } +// }; +// +// let cleaned: TokenStream = remove_attributes(input); +// +// let expected_output: TokenStream = quote! { +// pub struct MyStruct { +// pub field1: i32, +// pub field2: String, +// } +// }; +// +// assert_eq!(cleaned.to_string(), expected_output.to_string()); +// ``` +// +// In this example, the `#[derive(Debug, Clone)]` and `#[cfg(feature = "extra")]` +// attributes were removed, leaving just the plain `struct` with its fields. fn remove_attributes(item: TokenStream) -> TokenStream { let stream = item.into_iter(); let mut is_attribute = false; @@ -63,85 +176,76 @@ fn remove_attributes(item: TokenStream) -> TokenStream { TokenStream::from_iter(result) } +// Represents the current state of the parser while processing a struct. enum ParserState { + // Indicates that the parser is processing the struct's name. Name, + // Indicates that the parser is processing the struct's type. Type, - // open angle brackets + // Indicates that the parser is inside the angle brackets for generics. + // + // The `usize` value represents the depth of nested generics Generics(usize), } -fn parse_struct_fields(group: Vec) -> Vec { - let mut fields = Vec::new(); - let mut field_ = ParsedField::new(); - let mut field_parser_state = ParserState::Name; - for token in group { - match (token, &field_parser_state) { - (TokenTree::Ident(i), ParserState::Name) => { - if i.to_string() == "pub" { - continue; - } else { - field_.name = i.to_string(); - } - } - (TokenTree::Ident(i), ParserState::Type) => { - field_.type_ = i.to_string(); - } - (TokenTree::Ident(i), ParserState::Generics(_)) => { - field_.generics = format!("{}{}", field_.generics, i); - } - (TokenTree::Punct(p), ParserState::Name) => { - if p.to_string() == ":" { - field_parser_state = ParserState::Type - } else { - // Never executed at runtime it ok to panic - panic!("Unexpected token '{}' in parsing {:#?}", p, field_); - } - } - (TokenTree::Punct(p), ParserState::Type) => match p.to_string().as_ref() { - "," => { - field_parser_state = ParserState::Name; - fields.push(field_.clone()); - field_ = ParsedField::new(); - } - "<" => { - field_.generics = "<".to_string(); - field_parser_state = ParserState::Generics(0); - } - // Never executed at runtime it ok to panic - _ => panic!("Unexpected token '{}' in parsing {:#?}", p, field_), - }, - (TokenTree::Punct(p), ParserState::Generics(open_brackets)) => { - match p.to_string().as_ref() { - "'" => { - field_.generics = format!("{}{}", field_.generics, p); - } - "<" => { - field_.generics = format!("{}{}", field_.generics, p); - field_parser_state = ParserState::Generics(open_brackets + 1); - } - ">" => { - field_.generics = format!("{}{}", field_.generics, p); - if open_brackets == &0 { - field_parser_state = ParserState::Type - } else { - field_parser_state = ParserState::Generics(open_brackets - 1); - } - } - _ => { - field_.generics = format!("{}{}", field_.generics, p); - } - } - } - // Never executed at runtime it ok to panic - _ => panic!("Unexpected token"), - } - } - fields + +// Represents a parsed struct, including its name, generics, and fields. +// +// # Examples +// +// ```ignore +// struct MyStruct { +// pub field1: i32, +// pub field2: String, +// } +// +// let parsed = ParsedStruct { +// name: "MyStruct".to_string(), +// generics: "T".to_string(), +// fields: vec![ +// ParsedField { +// name: "field1".to_string(), +// type_: "i32".to_string(), +// generics: "".to_string(), +// }, +// ParsedField { +// name: "field2".to_string(), +// type_: "String".to_string(), +// generics: "".to_string(), +// }, +// ], +// }; +// ``` +#[derive(Clone, Debug)] +struct ParsedStruct { + // Name of the struct. + pub name: String, + // Generics associated with the struct, if any. + pub generics: String, + // List of fields within the struct. + pub fields: Vec, } +// Represents a parsed field within a struct, including its name, type, and any associated generics. +// +// # Examples +// +// ```ignore +// // Given a struct field definition: +// // data: Option, +// +// let field = ParsedField { +// name: "data".to_string(), +// type_: "Option".to_string(), +// generics: "T".to_string(), +// }; +// ``` #[derive(Clone, Debug)] struct ParsedField { + // Name of the field. name: String, + // Type of the field. type_: String, + // Generics associated with the field, if any. generics: String, } @@ -170,23 +274,33 @@ impl ParsedField { } } -#[derive(Clone, Debug)] -struct ParsedStruct { - pub name: String, - pub generics: String, - pub fields: Vec, -} - -// impl ParsedStruct { -// pub fn new() -> Self { -// ParsedStruct { -// name: "".to_string(), -// generics: "".to_string(), -// fields: Vec::new(), -// } -// } +// Extracts properties of a struct, including its name, generics, and fields. +// +// Processes a token stream, filtering out attributes and extracting +// core components such as the struct's name, generics, and fields. It expects the +// token stream to represent a struct declaration. +// +// # Examples +// +// ```ignore +// use quote::quote; +// +// struct MyStruct { +// field1: i32, // } - +// +// let tokens = quote! {struct MyStream { field1: i32 }}; +// let parsed_struct = get_struct_properties(tokens); +// +// assert_eq!(parsed_struct.name, "MyStruct"); +// assert_eq!(parsed_struct.generics, "T"); +// assert_eq!(parsed_struct.fields.len(), 1); +// assert_eq!(parsed_struct.fields[0].name, "field1"); +// assert_eq!(parsed_struct.fields[0].type_, "i32"); +// ``` +// +// This example demonstrates how `get_struct_properties` identifies the struct's +// name, any generic parameters, and its fields, including types and generic parameters. fn get_struct_properties(item: TokenStream) -> ParsedStruct { let item = remove_attributes(item); let mut stream = item.into_iter(); @@ -244,6 +358,201 @@ fn get_struct_properties(item: TokenStream) -> ParsedStruct { } } +// Parses the fields of a struct, scanning tokens to identify field names, types, and generics. +// +// Processes tokens for each field in a struct, managing parser states +// (`ParserState::Name`, `ParserState::Type`, and `ParserState::Generics`) to accurately parse +// complex types and nested generics within struct definitions. +// +// # Examples +// +// ```ignore +// struct MyStruct { +// field1: i32, +// } +// +// let tokens = vec![/* TokenTree representing `id: i32` */]; +// let parsed_fields = parse_struct_fields(tokens); +// +// assert_eq!(parsed_fields.len(), 1); +// assert_eq!(parsed_fields[0].name, "field1"); +// assert_eq!(parsed_fields[0].type_, "i32"); +// assert_eq!(parsed_fields[0].generics, ""); +// ``` +// +// This example shows how `parse_struct_fields` handles both a primitive field (`id`) and a +// generic field (`data`) within a struct, including parsing of generic parameters. +fn parse_struct_fields(group: Vec) -> Vec { + let mut fields = Vec::new(); + let mut field_ = ParsedField::new(); + let mut field_parser_state = ParserState::Name; + for token in group { + match (token, &field_parser_state) { + (TokenTree::Ident(i), ParserState::Name) => { + if i.to_string() == "pub" { + continue; + } else { + field_.name = i.to_string(); + } + } + (TokenTree::Ident(i), ParserState::Type) => { + field_.type_ = i.to_string(); + } + (TokenTree::Ident(i), ParserState::Generics(_)) => { + field_.generics = format!("{}{}", field_.generics, i); + } + (TokenTree::Punct(p), ParserState::Name) => { + if p.to_string() == ":" { + field_parser_state = ParserState::Type + } else { + // Never executed at runtime it ok to panic + panic!("Unexpected token '{}' in parsing {:#?}", p, field_); + } + } + (TokenTree::Punct(p), ParserState::Type) => match p.to_string().as_ref() { + "," => { + field_parser_state = ParserState::Name; + fields.push(field_.clone()); + field_ = ParsedField::new(); + } + "<" => { + field_.generics = "<".to_string(); + field_parser_state = ParserState::Generics(0); + } + // Never executed at runtime it ok to panic + _ => panic!("Unexpected token '{}' in parsing {:#?}", p, field_), + }, + (TokenTree::Punct(p), ParserState::Generics(open_brackets)) => { + match p.to_string().as_ref() { + "'" => { + field_.generics = format!("{}{}", field_.generics, p); + } + "<" => { + field_.generics = format!("{}{}", field_.generics, p); + field_parser_state = ParserState::Generics(open_brackets + 1); + } + ">" => { + field_.generics = format!("{}{}", field_.generics, p); + if open_brackets == &0 { + field_parser_state = ParserState::Type + } else { + field_parser_state = ParserState::Generics(open_brackets - 1); + } + } + _ => { + field_.generics = format!("{}{}", field_.generics, p); + } + } + } + // Never executed at runtime it ok to panic + _ => panic!("Unexpected token"), + } + } + fields +} + +/// Derives the `Decodable` trait, generating implementations for deserializing a struct from a +/// byte stream, including its structure, field decoding, and a method for creating a static +/// version. +/// +/// This procedural macro generates the `Decodable` trait for a struct, which allows it to be +/// decoded from a binary stream, with support for handling fields of different types and +/// nested generics. The macro also includes implementations for `from_decoded_fields`, +/// `get_structure`, and methods to return a static version of the struct. +/// +/// # Example +/// +/// Given a struct: +/// +/// ```ignore +/// struct Test { +/// a: u32, +/// b: u8, +/// c: U24, +/// } +/// ``` +/// +/// Using `#[derive(Decodable)]` on `Test` generates the following implementations: +/// +/// ```ignore +/// mod impl_parse_decodable_test { +/// use super::{ +/// binary_codec_sv2::{ +/// decodable::{DecodableField, FieldMarker}, +/// Decodable, Error, SizeHint, +/// }, +/// *, +/// }; +/// +/// struct Test { +/// a: u32, +/// b: u8, +/// c: U24, +/// } +/// +/// impl<'decoder> Decodable<'decoder> for Test { +/// fn get_structure(data: &[u8]) -> Result, Error> { +/// let mut fields = Vec::new(); +/// let mut offset = 0; +/// +/// let a: Vec = u32::get_structure(&data[offset..])?; +/// offset += a.size_hint_(&data, offset)?; +/// let a = a.try_into()?; +/// fields.push(a); +/// +/// let b: Vec = u8::get_structure(&data[offset..])?; +/// offset += b.size_hint_(&data, offset)?; +/// let b = b.try_into()?; +/// fields.push(b); +/// +/// let c: Vec = U24::get_structure(&data[offset..])?; +/// offset += c.size_hint_(&data, offset)?; +/// let c = c.try_into()?; +/// fields.push(c); +/// +/// Ok(fields) +/// } +/// +/// fn from_decoded_fields(mut data: Vec>) -> Result { +/// Ok(Self { +/// c: U24::from_decoded_fields( +/// data.pop().ok_or(Error::NoDecodableFieldPassed)?.into(), +/// )?, +/// b: u8::from_decoded_fields( +/// data.pop().ok_or(Error::NoDecodableFieldPassed)?.into(), +/// )?, +/// a: u32::from_decoded_fields( +/// data.pop().ok_or(Error::NoDecodableFieldPassed)?.into(), +/// )?, +/// }) +/// } +/// } +/// +/// impl Test { +/// pub fn into_static(self) -> Test { +/// Test { +/// a: self.a.clone(), +/// b: self.b.clone(), +/// c: self.c.clone(), +/// } +/// } +/// } +/// +/// impl Test { +/// pub fn as_static(&self) -> Test { +/// Test { +/// a: self.a.clone(), +/// b: self.b.clone(), +/// c: self.c.clone(), +/// } +/// } +/// } +/// } +/// ``` +/// +/// This generated code enables `Test` to be decoded from a binary stream, defines how each +/// field should be parsed, and provides `into_static` and `as_static` methods to facilitate +/// ownership and lifetime management of decoded fields in the struct. #[proc_macro_derive(Decodable)] pub fn decodable(item: TokenStream) -> TokenStream { let parsed_struct = get_struct_properties(item); @@ -383,6 +692,75 @@ fn get_static_generics(gen: &str) -> &str { } } +/// Derives the `Encodable` trait, generating implementations for serializing a struct into an +/// encoded format, including methods for field serialization, calculating the encoded size, +/// and handling cases where the struct is already sized. +/// +/// This procedural macro generates the `Encodable` trait for a struct, allowing each field +/// to be converted into an `EncodableField`, with support for recursive field encoding. +/// The macro also includes an implementation of the `GetSize` trait to calculate the +/// encoded size of the struct, depending on the `already_sized` attribute. +/// +/// # Example +/// +/// Given a struct: +/// +/// ```ignore +/// struct Test { +/// a: u32, +/// b: u8, +/// c: U24, +/// } +/// ``` +/// +/// Using `#[derive(Encodable)]` on `Test` generates the following implementations: +/// +/// ```ignore +/// mod impl_parse_encodable_test { +/// use super::binary_codec_sv2::{encodable::EncodableField, GetSize}; +/// extern crate alloc; +/// use alloc::vec::Vec; +/// +/// struct Test { +/// a: u32, +/// b: u8, +/// c: U24, +/// } +/// +/// impl<'decoder> From for EncodableField<'decoder> { +/// fn from(v: Test) -> Self { +/// let mut fields: Vec = Vec::new(); +/// +/// let val = v.a; +/// fields.push(val.into()); +/// +/// let val = v.b; +/// fields.push(val.into()); +/// +/// let val = v.c; +/// fields.push(val.into()); +/// +/// Self::Struct(fields) +/// } +/// } +/// +/// impl<'decoder> GetSize for Test { +/// fn get_size(&self) -> usize { +/// let mut size = 0; +/// +/// size += self.a.get_size(); +/// size += self.b.get_size(); +/// size += self.c.get_size(); +/// +/// size +/// } +/// } +/// } +/// ``` +/// +/// This generated code enables `Test` to be serialized into an encoded format, defines +/// how each field should be converted, and calculates the total encoded size of the struct, +/// depending on whether it is marked as `already_sized`. #[proc_macro_derive(Encodable, attributes(already_sized))] pub fn encodable(item: TokenStream) -> TokenStream { let is_already_sized = is_already_sized(item.clone()); diff --git a/protocols/v2/sv2-ffi/sv2.h b/protocols/v2/sv2-ffi/sv2.h index e899b44e7..9e7dd8a99 100644 --- a/protocols/v2/sv2-ffi/sv2.h +++ b/protocols/v2/sv2-ffi/sv2.h @@ -242,41 +242,56 @@ static const bool CHANNEL_BIT_UPDATE_CHANNEL_ERROR = true; #include #include +/// A struct to facilitate transferring a `Vec` across FFI boundaries. struct CVec { uint8_t *data; uintptr_t len; uintptr_t capacity; }; +/// A struct to manage a collection of `CVec` objects across FFI boundaries. struct CVec2 { CVec *data; uintptr_t len; uintptr_t capacity; }; +/// Represents a 24-bit unsigned integer (`U24`), supporting SV2 serialization and deserialization. +/// Only first 3 bytes of a u32 is considered to get the SV2 value, and rest are ignored (in little +/// endian). struct U24 { uint32_t _0; }; extern "C" { -/// Given a C allocated buffer return a rust allocated CVec +/// Creates a `CVec` from a buffer that was allocated in C. /// /// # Safety +/// The caller must ensure that the buffer is valid and that +/// the data length does not exceed the allocated size. CVec cvec_from_buffer(const uint8_t *data, uintptr_t len); +/// Initializes an empty `CVec2`. +/// /// # Safety +/// The caller is responsible for freeing the `CVec2` when it is no longer needed. CVec2 init_cvec2(); -/// The caller is reponsible for NOT adding duplicate cvecs to the cvec2 structure, -/// as this can lead to double free errors when the message is dropped. +/// Adds a `CVec` to a `CVec2`. +/// /// # Safety +/// The caller must ensure no duplicate `CVec`s are added, as duplicates may +/// lead to double-free errors when the message is dropped. void cvec2_push(CVec2 *cvec2, CVec cvec); +/// Exported FFI functions for interoperability with C code for u24 void _c_export_u24(U24 _a); +/// Exported FFI functions for interoperability with C code for CVec void _c_export_cvec(CVec _a); +/// Exported FFI functions for interoperability with C code for CVec2 void _c_export_cvec2(CVec2 _a); } // extern "C"