From 288feb52627ea45c9422c14f52becea23cc89cfd Mon Sep 17 00:00:00 2001 From: RJ Rybarczyk Date: Mon, 14 Oct 2024 21:25:05 -0400 Subject: [PATCH] framing mod doc cmts + clean --- protocols/v2/framing-sv2/src/error.rs | 5 ++ protocols/v2/framing-sv2/src/framing.rs | 105 ++++++++++++++++-------- protocols/v2/framing-sv2/src/header.rs | 49 +++++++++-- protocols/v2/framing-sv2/src/lib.rs | 58 +++++++++---- 4 files changed, 160 insertions(+), 57 deletions(-) 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 8ac44339fb..6153c3d41e 100644 --- a/protocols/v2/framing-sv2/src/framing.rs +++ b/protocols/v2/framing-sv2/src/framing.rs @@ -1,3 +1,20 @@ +//! # 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 [`crate::framing::Sv2Frame`] and is +//! used for almost all messages passed between Sv2 roles. It consists of a +//! [`crate::header::Header`] followed by the serialized message payload. The +//! [`crate::framing::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 +//! [`crate::framing::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 +26,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 +58,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 +72,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 +98,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..] @@ -87,15 +115,17 @@ impl + AsRef<[u8]>> Sv2Frame { } } - /// `Sv2Frame` always returns `Some(self.header)`. + /// [`Sv2Frame`] always returns `Some(self.header)`. pub fn get_header(&self) -> Option { 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()); @@ -107,6 +137,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 @@ -118,8 +149,8 @@ impl + AsRef<[u8]>> Sv2Frame { } } - /// After parsing `bytes` into a `Header`, this function helps to determine if the `msg_length` - /// field is correctly representing the size of the frame. + /// After parsing `bytes` into a [`Header`], this function helps to determine if the + /// `msg_length` field is correctly representing the size of the frame. /// - Returns `0` if the byte slice is of the expected size according to the header. /// - Returns a negative value if the byte slice is shorter than expected; this value represents /// how many bytes are missing. @@ -144,8 +175,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() { @@ -158,8 +189,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, @@ -177,8 +209,8 @@ impl + AsRef<[u8]>> Sv2Frame { } impl Sv2Frame { - /// Maps a `Sv2Frame` to `Sv2Frame` by applying `fun`, - /// which is assumed to be a closure that converts `A` to `C` + /// Maps a `Sv2Frame` to `Sv2Frame` by applying `fun`, which is assumed to be a + /// closure that converts `A` to `C` pub fn map(self, fun: fn(A) -> C) -> Sv2Frame { let serialized = self.serialized; let header = self.header; @@ -202,21 +234,23 @@ 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 the serialized 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, } impl HandShakeFrame { - /// Returns payload of `HandShakeFrame` as a `Vec` + /// Returns payload of [`HandShakeFrame`] as a [`Vec`]. pub fn get_payload_when_handshaking(&self) -> Vec { self.payload[0..].to_vec() } - /// Builds a `HandShakeFrame` from raw bytes. Nothing is assumed or checked about the + /// Builds a [`HandShakeFrame`] from raw bytes. Nothing is assumed or checked about the /// correctness of the payload. pub fn from_bytes(bytes: Slice) -> Result { Ok(Self::from_bytes_unchecked(bytes)) @@ -227,7 +261,7 @@ impl HandShakeFrame { Self { payload: bytes } } - // Returns the size of the `HandShakeFrame` payload. + // Returns the size of the [`HandShakeFrame`] payload. #[inline] fn encoded_length(&self) -> usize { self.payload.len() @@ -245,7 +279,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(); @@ -255,10 +289,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..306c1dff7c 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 [`crate::header::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; @@ -14,7 +35,7 @@ pub const NOISE_HEADER_LEN_OFFSET: usize = const_sv2::NOISE_FRAME_HEADER_LEN_OFF // Previously `NoiseHeader::HEADER_SIZE` pub const NOISE_HEADER_SIZE: usize = const_sv2::NOISE_FRAME_HEADER_SIZE; -/// Abstraction for a SV2 Frame Header. +/// Abstraction for a Sv2 Frame Header. #[derive(Debug, Serialize, Deserialize, Copy, Clone)] pub struct Header { // Unique identifier of the extension describing this protocol message. Most significant bit @@ -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, } @@ -35,7 +58,7 @@ pub struct Header { impl Header { pub const SIZE: usize = const_sv2::SV2_FRAME_HEADER_SIZE; - /// Construct a `Header` from raw bytes + /// Construct a [`Header`] from raw bytes #[inline] pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() < Self::SIZE { @@ -59,7 +82,7 @@ impl Header { inner as usize } - // Construct a `Header` from payload length, type and extension type. + // Construct a [`Header`] from payload length, type and extension type. #[inline] pub(crate) fn from_len(msg_length: u32, msg_type: u8, extension_type: u16) -> Option
{ Some(Self { @@ -69,17 +92,17 @@ impl Header { }) } - /// Get the `Header` message type. + /// Get the [`Header`] message type. pub fn msg_type(&self) -> u8 { self.msg_type } - /// Get the `Header` extension type. + /// Get the [`Header`[ extension type. pub fn ext_type(&self) -> u16 { self.extension_type } - /// Check if `Header` represents a channel message + /// Check if [`Header`] represents a channel message. /// /// A header can represent a channel message if the MSB(Most Significant Bit) is set. pub fn channel_msg(&self) -> bool { @@ -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 4096384a93..4ea121f080 100644 --- a/protocols/v2/framing-sv2/src/lib.rs +++ b/protocols/v2/framing-sv2/src/lib.rs @@ -1,10 +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 | //! |----------------|-------------|-------------| @@ -13,26 +19,48 @@ //! | `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::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`](https://crates.io/crates/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 example for more information: +//! +//! - [Sv2 Frame Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/framing-sv2/examples/sv2_frame.rs) #![no_std] extern crate alloc; -/// SV2 framing types +/// Sv2 framing types pub mod framing; -/// SV2 framing errors +/// Sv2 framing errors pub mod error; -/// SV2 framing header +/// Sv2 framing header pub mod header; pub use error::Error;