Skip to content

Commit

Permalink
Merge pull request #1231 from Shourya742/doc-binary-sv2
Browse files Browse the repository at this point in the history
doc binary-sv2
  • Loading branch information
GitGab19 authored Dec 9, 2024
2 parents 5bcc26e + 0ba42e4 commit b32485d
Show file tree
Hide file tree
Showing 16 changed files with 1,533 additions and 151 deletions.
29 changes: 27 additions & 2 deletions protocols/v2/binary-sv2/binary-sv2/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
# binary_sv2
# binary-sv2

`binary_sv2` is a Rust `no_std` crate
[![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
```
118 changes: 118 additions & 0 deletions protocols/v2/binary-sv2/binary-sv2/examples/encode_decode.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<FieldMarker>, Error> {
// let mut fields = Vec::new();
// let mut offset = 0;
// let a: Vec<FieldMarker> = u32::get_structure(&data[offset..])?;
// offset += a.size_hint_(&data, offset)?;
// let a = a.try_into()?;
// fields.push(a);
// let b: Vec<FieldMarker> = u8::get_structure(&data[offset..])?;
// offset += b.size_hint_(&data, offset)?;
// let b = b.try_into()?;
// fields.push(b);
// let c: Vec<FieldMarker> = 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<DecodableField<'decoder>>,
// ) -> Result<Self, Error> {
// 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<Test> for EncodableField<'decoder> {
// fn from(v: Test) -> Self {
// let mut fields: Vec<EncodableField> = 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);
}
23 changes: 21 additions & 2 deletions protocols/v2/binary-sv2/binary-sv2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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: Serialize>(_: T) -> T {
todo!()
}

/// Converts a value implementing the `Into<u64>` trait into a custom `U256` type.
pub fn u256_from_int<V: Into<u64>>(value: V) -> U256<'static> {
// initialize u256 as a bytes vec of len 24
let mut u256 = vec![0_u8; 24];
Expand Down
43 changes: 42 additions & 1 deletion protocols/v2/binary-sv2/no-serde-sv2/codec/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
# binary_codec_sv2

`binary_codec_sv2` is a Rust `no_std` crate
[![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
```
70 changes: 62 additions & 8 deletions protocols/v2/binary-sv2/no-serde-sv2/codec/src/codec/decodable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,28 @@ use core::convert::TryFrom;
#[cfg(not(feature = "no_std"))]
use std::io::{Cursor, Read};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

impl FieldMarker {
// Implements the decoding functionality for a `FieldMarker`.
// Depending on whether the field is primitive or structured, this method decodes the
// corresponding data. If the field is a structure, it recursively decodes each nested field
// and returns the resulting `DecodableField`.
pub(crate) fn decode<'a>(&self, data: &'a mut [u8]) -> Result<DecodableField<'a>, Error> {
match self {
Self::Primitive(p) => Ok(DecodableField::Primitive(p.decode(data, 0))),
Expand Down
Loading

0 comments on commit b32485d

Please sign in to comment.