Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

doc binary-sv2 #1231

Merged
merged 9 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Shourya742 marked this conversation as resolved.
Show resolved Hide resolved
- **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.
//!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encoding and decoding what? I think the messages should be mentioned here, and then point in which step in the flow of stratum v2 message exchanging binary-sv2 can be used

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually this crate is completely agnostic to subprotocols/* crates

all encoding and decoding happen around basic Sv2 types

the fact that those are used to compose Sv2 messages happen at a higher level of abstraction

Copy link
Contributor

@jbesraa jbesraa Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea thats exactly what I think should mentioned.

"This crate helps converting between SV2 types and Rust native types to handle SV2 messages. You can use the library in order to serialize sv2 messages into binary representations or deserialize binary data into an sv2 message"

..maybe something like this

//! # 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
Shourya742 marked this conversation as resolved.
Show resolved Hide resolved
//! `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"))]
Shourya742 marked this conversation as resolved.
Show resolved Hide resolved
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
Shourya742 marked this conversation as resolved.
Show resolved Hide resolved
/// back into the original type, handling nested structures or composite fields.
fn from_decoded_fields(data: Vec<DecodableField<'a>>) -> Result<Self, Error>;
Shourya742 marked this conversation as resolved.
Show resolved Hide resolved

/// 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> {
Shourya742 marked this conversation as resolved.
Show resolved Hide resolved
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 {
Shourya742 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading