Skip to content

Commit

Permalink
framing mod doc cmts + clean
Browse files Browse the repository at this point in the history
  • Loading branch information
rrybarczyk committed Oct 15, 2024
1 parent 55eaee9 commit 376ffa4
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 35 deletions.
5 changes: 5 additions & 0 deletions protocols/v2/framing-sv2/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
//! # Error Handling
//!
//! This module defines error types and utilities for handling errors in the `framing_sv2` module.
// use crate::framing2::EitherFrame;
use core::fmt;

// pub type FramingResult<T> = core::result::Result<T, Error>;

#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// Binary Sv2 data format error.
BinarySv2Error(binary_sv2::Error),
ExpectedHandshakeFrame,
ExpectedSv2Frame,
Expand Down
87 changes: 61 additions & 26 deletions protocols/v2/framing-sv2/src/framing.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -9,8 +25,11 @@ type Slice = Vec<u8>;
#[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<T, B> {
HandShake(HandShakeFrame),
Expand Down Expand Up @@ -38,7 +57,11 @@ impl<T, B> From<Sv2Frame<T, B>> for Frame<T, B> {
}
}

/// 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<T, B> {
header: Header,
Expand All @@ -48,9 +71,10 @@ pub struct Sv2Frame<T, B> {
}

impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
/// 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 {
Expand All @@ -73,11 +97,14 @@ impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
}
}

/// `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..]
Expand All @@ -92,9 +119,12 @@ impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
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<Self, isize> {
let hint = Self::size_hint(bytes.as_mut());
Expand All @@ -106,6 +136,7 @@ impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
}
}

/// 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
Expand Down Expand Up @@ -143,8 +174,8 @@ impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
}
}

/// 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() {
Expand All @@ -157,8 +188,9 @@ impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
}
}

/// 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,
Expand Down Expand Up @@ -201,9 +233,11 @@ impl<T, B> TryFrom<Frame<T, B>> for Sv2Frame<T, B> {
}
}

/// 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,
Expand Down Expand Up @@ -243,7 +277,7 @@ impl<T, B> TryFrom<Frame<T, B>> 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<T: AsRef<[u8]>>(message: T) -> HandShakeFrame {
let mut payload = Vec::new();
Expand All @@ -253,10 +287,11 @@ pub fn handshake_message_to_frame<T: AsRef<[u8]>>(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;
Expand Down
37 changes: 36 additions & 1 deletion protocols/v2/framing-sv2/src/header.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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);
Expand Down
49 changes: 41 additions & 8 deletions protocols/v2/framing-sv2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 |
//! |----------------|-------------|-------------|
Expand All @@ -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;
Expand Down

0 comments on commit 376ffa4

Please sign in to comment.