diff --git a/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/Cargo.toml b/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/Cargo.toml index c7884c73af..ba920a7832 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/Cargo.toml +++ b/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/Cargo.toml @@ -19,5 +19,9 @@ binary_codec_sv2 = {version = "^1.0.0", path="../codec"} [lib] proc-macro = true +[dev-dependencies] +proc-macro2 = "1.0.89" +quote = "1.0.37" + [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true 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 new file mode 100644 index 0000000000..b0401660b2 --- /dev/null +++ b/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/README.md @@ -0,0 +1,22 @@ +# derive-codec-sv2 + +[![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) + +`derive-codec-sv2` is a 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 58dc903569..b4d6027639 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,9 +1,74 @@ +//! # Procedural Macros for Automatic Serialization and Deserialization +//! +//! This module 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. + extern crate proc_macro; 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`. +// Checks if a `TokenStream` contains a group with a bracket delimiter (`[]`), +// and further examines if the group has an identifier called `already_sized`. +// +// This function 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 +// +// ```rust +// use proc_macro2::{TokenStream, TokenTree, Group, Delimiter, Ident, Span}; +// 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(); @@ -23,8 +88,42 @@ fn is_already_sized(item: TokenStream) -> bool { false } -/// Filters out attributes from a `TokenStream` that are prefixed with `#`, -/// These attributes are typically used for meta-information in Rust code. +// Filters out attributes from a `TokenStream` that are prefixed with `#`. +// +// This function 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 +// +// ```rust +// use proc_macro2::{TokenStream, TokenTree, Group, Delimiter}; +// 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; @@ -59,93 +158,80 @@ 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), } -/// Parses the fields of a struct, scanning tokens to identify names,types and generics. -/// Tracks and manages different parser states to handle field parsing, including names, types, and generic delimiters. -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 +// +// ``` +// 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. -/// Each field contains a name, type, and any associated generics. +// Represents a parsed field within a struct, including its name, type, and any associated generics. +// +// # Examples +// +// ``` +// // 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, } + impl ParsedField { pub fn new() -> Self { ParsedField { @@ -171,25 +257,34 @@ 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. +// +// This method 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 +// +// ``` +// use quote::quote; +// +// struct MyStruct { +// field1: i32, // } - -/// Extracts and returns properties of a struct, including its name, generics, and fields. -/// Parses the token stream after filtering out attributes. +// +// 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(); @@ -247,14 +342,201 @@ fn get_struct_properties(item: TokenStream) -> ParsedStruct { } } -/// The `Decodable` macro automatically derives the `Decodable` trait for -/// structs, allowing them to be initialized from byte slice. It utilizes -/// the provided data to populate fields of the struct and constructs a vector -/// of `FieldMaker` for structure representation. During the decoding process, -/// it reads the encoded fields from the input data, updating the offset -/// accordingly to ensure that all data is read correctly. The macro generates -/// implementations for the `get_structure` and `from_decoded_fields` methods, -/// facilitating the decoding of complex structure into RUST types. +// Parses the fields of a struct, scanning tokens to identify field names, types, and generics. +// +// This method 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 +// +// ``` +// 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: +/// +/// ``` +/// struct Test { +/// a: u32, +/// b: u8, +/// c: U24, +/// } +/// ``` +/// +/// Using `#[derive(Decodable)]` on `Test` generates the following implementations: +/// +/// ```rust +/// mod impl_parse_decodable_test { +/// use super::binary_codec_sv2::{ +/// decodable::DecodableField, decodable::FieldMarker, Decodable, Error, SizeHint, +/// }; +/// use super::*; +/// +/// 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); @@ -394,14 +676,75 @@ fn get_static_generics(gen: &str) -> &str { } } -/// The `Encodable` macro derives the `Encodable` trait for structs, enabling -/// them to be converted into a sequence of bytes. It transforms each field into a -/// `DecodableField` and computes the total size of the encoded data. The macro -/// creates an implementation of `From` to convert the struct into an `EncodableField` -/// , which can then be serialized into byte streams. It also optionally implements the -/// `GetSize` trait for types that require additional size computations, based on the -/// `already_sized` attribute. This way. users can easily encode their custom types into -/// a binary format sutiable for network transmission +/// 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: +/// +/// ``` +/// struct Test { +/// a: u32, +/// b: u8, +/// c: U24, +/// } +/// ``` +/// +/// Using `#[derive(Encodable)]` on `Test` generates the following implementations: +/// +/// ```rust +/// 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());