diff --git a/protocols/v2/framing-sv2/src/error.rs b/protocols/v2/framing-sv2/src/error.rs index 44b0c95cef..cef6ab410c 100644 --- a/protocols/v2/framing-sv2/src/error.rs +++ b/protocols/v2/framing-sv2/src/error.rs @@ -1,3 +1,7 @@ +//! # Error Handling +//! +//! This module defines error types and utilities for handling errors in the `framing_sv2` module. + // use crate::framing2::EitherFrame; use core::fmt; @@ -5,6 +9,7 @@ use core::fmt; #[derive(Debug, PartialEq, Eq)] pub enum Error { + /// Binary Sv2 data format error. BinarySv2Error(binary_sv2::Error), ExpectedHandshakeFrame, ExpectedSv2Frame, diff --git a/protocols/v2/framing-sv2/src/framing.rs b/protocols/v2/framing-sv2/src/framing.rs index 763e714640..7fc6b8ae90 100644 --- a/protocols/v2/framing-sv2/src/framing.rs +++ b/protocols/v2/framing-sv2/src/framing.rs @@ -1,3 +1,19 @@ +//! # Sv2 Frame +//! +//! Handles the serializing and deserializing of both Sv2 and Noise handshake messages into frames. +//! +//! It handles the serialization and deserialization of frames, ensuring that messages can be +//! correctly encoded and transmitted and then received and decoded between Sv2 roles. +//! +//! # Usage +//! +//! Two types of frames are defined. The most common frame is [`Sv2Frame`] and is used for almost +//! all messages passed between Sv2 roles. It consists of a [`Header`] followed by the serialized +//! message payload. The [`HandShakeFrame`] is used exclusively during the Noise handshake process, +//! performed between Sv2 roles at the beginning of their communication. This frame is used until +//! the handshake state progresses to transport mode. After that, all subsequent messages use +//! [`Sv2Frame`]. No header is included in the handshake frame. + use crate::{header::Header, Error}; use alloc::vec::Vec; use binary_sv2::{to_writer, GetSize, Serialize}; @@ -9,8 +25,11 @@ type Slice = Vec; #[cfg(feature = "with_buffer_pool")] type Slice = buffer_sv2::Slice; -/// A wrapper to be used in a context we need a generic reference to a frame -/// but it doesn't matter which kind of frame it is (`Sv2Frame` or `HandShakeFrame`) +/// Represents either an Sv2 frame or a handshake frame. +/// +/// A wrapper used when generic reference to a frame is needed, but the kind of frame ([`Sv2Frame`] +/// or [`HandShakeFrame`]) does not matter. Note that after the initial handshake is complete +/// between two Sv2 roles, all further messages are framed with [`Sv2Frame`]. #[derive(Debug)] pub enum Frame { HandShake(HandShakeFrame), @@ -38,7 +57,11 @@ impl From> for Frame { } } -/// Abstraction for a SV2 Frame. +/// Abstraction for a Sv2 frame. +/// +/// Represents a regular SV2 frame, used for all communication outside of the Noise protocol +/// handshake process. It contains a [`Header`] and a message payload, which can be serialized for +/// encoding and transmission or decoded and deserialized upon receipt. #[derive(Debug, Clone)] pub struct Sv2Frame { header: Header, @@ -48,9 +71,10 @@ pub struct Sv2Frame { } impl + AsRef<[u8]>> Sv2Frame { - /// Write the serialized `Sv2Frame` into `dst`. - /// This operation when called on an already serialized frame is very cheap. - /// When called on a non serialized frame, it is not so cheap (because it serializes it). + /// Writes the serialized `Sv2Frame` into `dst`. + /// + /// This operation when called on an already serialized frame is very cheap. When called on a + /// non serialized frame, it is not so cheap (because it serializes it). #[inline] pub fn serialize(self, dst: &mut [u8]) -> Result<(), Error> { if let Some(mut serialized) = self.serialized { @@ -73,11 +97,14 @@ impl + AsRef<[u8]>> Sv2Frame { } } - /// `self` can be either serialized (`self.serialized` is `Some()`) or - /// deserialized (`self.serialized` is `None`, `self.payload` is `Some()`). - /// This function is only intended as a fast way to get a reference to an - /// already serialized payload. If the frame has not yet been - /// serialized, this function should never be used (it will panic). + /// Returns the message payload. + /// + /// `self` can be either serialized (`self.serialized` is `Some()`) or deserialized + /// (`self.serialized` is `None`, `self.payload` is `Some()`). + /// + /// This function is only intended as a fast way to get a reference to an already serialized + /// payload. If the frame has not yet been serialized, this function should never be used (it + /// will panic). pub fn payload(&mut self) -> &mut [u8] { if let Some(serialized) = self.serialized.as_mut() { &mut serialized.as_mut()[Header::SIZE..] @@ -92,9 +119,12 @@ impl + AsRef<[u8]>> Sv2Frame { Some(self.header) } - /// Tries to build a `Sv2Frame` from raw bytes, assuming they represent a serialized `Sv2Frame` frame (`Self.serialized`). - /// Returns a `Sv2Frame` on success, or the number of the bytes needed to complete the frame - /// as an error. `Self.serialized` is `Some`, but nothing is assumed or checked about the correctness of the payload. + /// Tries to build a [`Sv2Frame`] from raw bytes. + /// + /// It assumes the raw bytes represent a serialized [`Sv2Frame`] frame (`Self.serialized`). + /// Returns a [`Sv2Frame`] on success, or the number of the bytes needed to complete the frame + /// as an error. `Self.serialized` is [`Some`], but nothing is assumed or checked about the + /// correctness of the payload. #[inline] pub fn from_bytes(mut bytes: B) -> Result { let hint = Self::size_hint(bytes.as_mut()); @@ -106,6 +136,7 @@ impl + AsRef<[u8]>> Sv2Frame { } } + /// Constructs an [`Sv2Frame`] from raw bytes without performing byte content validation. #[inline] pub fn from_bytes_unchecked(mut bytes: B) -> Self { // Unchecked function caller is supposed to already know that the passed bytes are valid @@ -143,8 +174,8 @@ impl + AsRef<[u8]>> Sv2Frame { } } - /// If `Sv2Frame` is serialized, returns the length of `self.serialized`, - /// otherwise, returns the length of `self.payload`. + /// If [`Sv2Frame`] is serialized, returns the length of `self.serialized`, otherwise, returns + /// the length of `self.payload`. #[inline] pub fn encoded_length(&self) -> usize { if let Some(serialized) = self.serialized.as_ref() { @@ -157,8 +188,9 @@ impl + AsRef<[u8]>> Sv2Frame { } } - /// Tries to build a `Sv2Frame` from a non-serialized payload. - /// Returns a `Sv2Frame` if the size of the payload fits in the frame, `None` otherwise. + /// Tries to build a [`Sv2Frame`] from a non-serialized payload. + /// + /// Returns a [`Sv2Frame`] if the size of the payload fits in the frame, [`None`] otherwise. pub fn from_message( message: T, message_type: u8, @@ -201,9 +233,11 @@ impl TryFrom> for Sv2Frame { } } -/// Abstraction for a Noise Handshake Frame -/// Contains only a `Slice` payload with a fixed length -/// Only used during Noise Handshake process +/// Abstraction for a Noise handshake frame. +/// +/// Contains only a `Slice` payload with a fixed length and is only used during Noise Handshake +/// process. Once the handshake is complete, regular Sv2 communication switches to [`Sv2Frame`] for +/// ongoing communication. #[derive(Debug)] pub struct HandShakeFrame { payload: Slice, @@ -243,7 +277,7 @@ impl TryFrom> for HandShakeFrame { } } -/// Returns a `HandShakeFrame` from a generic byte array +/// Returns a `HandShakeFrame` from a generic byte array. #[allow(clippy::useless_conversion)] pub fn handshake_message_to_frame>(message: T) -> HandShakeFrame { let mut payload = Vec::new(); @@ -253,10 +287,11 @@ pub fn handshake_message_to_frame>(message: T) -> HandShakeFrame } } -// Basically a boolean bit filter for `extension_type`. -// Takes an `extension_type` represented as a `u16` and a boolean flag (`channel_msg`). -// If `channel_msg` is true, it sets the most significant bit of `extension_type` to 1, -// otherwise, it clears the most significant bit to 0. +// Basically a Boolean bit filter for `extension_type`. +// +// Takes an `extension_type` represented as a `u16` and a boolean flag (`channel_msg`). If +// `channel_msg` is true, it sets the most significant bit of `extension_type` to `1`, otherwise, +// it clears the most significant bit to `0`. fn update_extension_type(extension_type: u16, channel_msg: bool) -> u16 { if channel_msg { let mask = 0b1000_0000_0000_0000; diff --git a/protocols/v2/framing-sv2/src/header.rs b/protocols/v2/framing-sv2/src/header.rs index 178dc9f13e..98fd4f95bf 100644 --- a/protocols/v2/framing-sv2/src/header.rs +++ b/protocols/v2/framing-sv2/src/header.rs @@ -1,3 +1,24 @@ +//! # Sv2 Frame Header +//! +//! Defines the [`Header`] structure used in the framing of Sv2 messages. +//! +//! Each [`crate::framing::Sv2Frame`] starts with a 6-byte header with information about the +//! message payload, including its extension type, if it is associated with a specific mining +//! channel, the type of message (e.g. `SetupConnection`, `NewMiningJob`, etc.) and the payload +//! length. +//! +//! ## Header Structure +//! +//! The Sv2 `Header` includes the following fields: +//! +//! - `extension_type`: A `16`-bit field that describes the protocol extension associated with the +//! message. It also contains a special bit (the `channel_msg` bit) to indicate if the message is +//! tied to a specific channel. +//! - `msg_type`: An `8`-bit field representing the specific message type within the given +//! extension. +//! - `msg_length`: A `24`-bit field that indicates the length of the message payload, excluding +//! the header itself. + use crate::Error; #[cfg(not(feature = "with_serde"))] use alloc::vec::Vec; @@ -26,8 +47,10 @@ pub struct Header { // channel_id this message is destined for. Note that for the Job Declaration and Template // Distribution Protocols the channel_msg bit is always unset. extension_type: u16, // fix: use U16 type + // // Unique identifier of the extension describing this protocol message msg_type: u8, // fix: use specific type? + // Length of the protocol message, not including this header msg_length: U24, } @@ -87,7 +110,19 @@ impl Header { self.extension_type & CHANNEL_MSG_MASK == self.extension_type } - /// Calculate the length of the encrypted `Header` + /// Calculates the total length of a chunked message, accounting for MAC overhead. + /// + /// Determines the total length of the message frame, including the overhead introduced by + /// MACs. If the message is split into multiple chunks (due to its size exceeding the maximum + /// frame chunk size), each chunk requires a MAC for integrity verification. + /// + /// This method is particularly relevant when the message is being encrypted using the Noise + /// protocol, where the payload is divided into encrypted chunks, and each chunk is appended + /// with a MAC. However, it can also be applied to non-encrypted chunked messages to calculate + /// their total length. + /// + /// The calculated length includes the full payload length and any additional space required + /// for the MACs. pub fn encrypted_len(&self) -> usize { let len = self.len(); let mut chunks = len / (SV2_FRAME_CHUNK_SIZE - AEAD_MAC_LEN); diff --git a/protocols/v2/framing-sv2/src/lib.rs b/protocols/v2/framing-sv2/src/lib.rs index cf792e65da..c2cae0d52d 100644 --- a/protocols/v2/framing-sv2/src/lib.rs +++ b/protocols/v2/framing-sv2/src/lib.rs @@ -1,9 +1,16 @@ -//! The SV2 protocol is binary, with fixed message framing. -//! Each message begins with the extension type, message type, and message length (six bytes in total), followed by a variable length message. +//! # Stratum V2 Framing Library //! -//! This crate provides primitives for framing of SV2 binary messages. +//! `framing_sv2` provides utilities for framing messages sent between Sv2 roles, handling both Sv2 +//! message and Noise handshake frames. //! -//! The message framing is outlined below ([according to SV2 specs](https://stratumprotocol.org/specification/03-Protocol-Overview/#32-framing)): +//! ## Message Format +//! +//! The Sv2 protocol is binary, with fixed message framing. Each message begins with the extension +//! type, message type, and message length (six bytes in total), followed by a variable length +//! message. +//! +//! The message framing is outlined below ([according to SV2 specs +//! ](https://stratumprotocol.org/specification/03-Protocol-Overview/#32-framing)): //! //! | Protocol Type | Byte Length | Description | //! |----------------|-------------|-------------| @@ -12,12 +19,38 @@ //! | `msg_length` | `U24` | Length of the protocol message, not including this header. | //! | `payload` | `BYTES` | Message-specific payload of length `msg_length`. If the MSB in `extension_type` (the `channel_msg` bit) is set the first four bytes are defined as a `U32` `"channel_id"`, though this definition is repeated in the message definitions below and these 4 bytes are included in `msg_length`. | //! -//! # Features +//! +//! ## Usage +//! +//! Nearly all messages sent between Sv2 roles are serialized with the [`framing::Sv2Frame`]. The +//! exception is when two Sv2 roles exchange Noise protocol handshake messages. +//! +//! Before Sv2 roles can communicate securely, they must perform a Noise handshake (note that Noise +//! encryption is optional for communication between two local Sv2 roles (i.e. a local mining +//! device and a local mining proxy), but required between two remote Sv2 roles (i.e. a local +//! mining proxy and a remote pool)). During this process, the [`framing::HandShakeFrame`] is used +//! to transmit encrypted messages between the roles. After the handshake is completed and the +//! connection transitions into transport mode, [`framing::Sv2Frame`] is used for all messages. +//! +//! Once the Noise handshake is complete (if it was performed at all), all subsequent messages are +//! framed using the [`framing::Sv2Frame`]. Each frame consists of a [`Header`] followed by a +//! serialized payload. +//! +//! ## Build Options +//! //! This crate can be built with the following features: -//! - `with_serde`: builds `binary_sv2` and `buffer_sv2` crates with `serde`-based encoding and decoding. -//! - `with_buffer_pool`: uses `buffer_sv2` to provide a more efficient allocation method for `non_std` environments. Please refer to `buffer_sv2` crate docs for more context. //! -//! The `with_serde` feature flag is only used for the Message Generator, and deprecated for any other kind of usage. It will likely be fully deprecated in the future. +//! - `with_buffer_pool`: Enables buffer pooling for more efficient memory management. +//! - `with_serde`: builds [`binary_sv2`] and [`buffer_sv2`] crates with `serde`-based encoding and +//! decoding. Note that this feature flag is only used for the Message Generator, and deprecated +//! for any other kind of usage. It will likely be fully deprecated in the future. +//! +//! ## Examples +//! +//! See the examples for more information: +//! +//! - [Sv2 Frame Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/framing-sv2/examples/sv2_frame.rs) +//! - [Noise Handshake Frame Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/framing-sv2/examples/handshake_frame.rs) #![cfg_attr(feature = "no_std", no_std)] extern crate alloc;