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 Dec 18, 2024
1 parent 39cc837 commit 95471a0
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 57 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
105 changes: 70 additions & 35 deletions protocols/v2/framing-sv2/src/framing.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -9,8 +26,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 +58,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 +72,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 +98,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 @@ -87,15 +115,17 @@ impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
}
}

/// `Sv2Frame` always returns `Some(self.header)`.
/// [`Sv2Frame`] always returns `Some(self.header)`.
pub fn get_header(&self) -> Option<crate::header::Header> {
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 @@ -107,6 +137,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 All @@ -118,8 +149,8 @@ impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
}
}

/// 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.
Expand All @@ -144,8 +175,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 @@ -158,8 +189,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 All @@ -177,8 +209,8 @@ impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
}

impl<A, B> Sv2Frame<A, B> {
/// Maps a `Sv2Frame<A, B>` to `Sv2Frame<C, B>` by applying `fun`,
/// which is assumed to be a closure that converts `A` to `C`
/// Maps a `Sv2Frame<A, B>` to `Sv2Frame<C, B>` by applying `fun`, which is assumed to be a
/// closure that converts `A` to `C`
pub fn map<C>(self, fun: fn(A) -> C) -> Sv2Frame<C, B> {
let serialized = self.serialized;
let header = self.header;
Expand All @@ -202,21 +234,23 @@ 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 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<u8>`
/// Returns payload of [`HandShakeFrame`] as a [`Vec<u8>`].
pub fn get_payload_when_handshaking(&self) -> Vec<u8> {
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<Self, isize> {
Ok(Self::from_bytes_unchecked(bytes))
Expand All @@ -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()
Expand All @@ -245,7 +279,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 @@ -255,10 +289,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
49 changes: 42 additions & 7 deletions 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 [`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;
Expand All @@ -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
Expand All @@ -26,16 +47,18 @@ 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,
}

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<Self, Error> {
if bytes.len() < Self::SIZE {
Expand All @@ -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<Header> {
Some(Self {
Expand All @@ -69,25 +92,37 @@ 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 {
const CHANNEL_MSG_MASK: u16 = 0b0000_0000_0000_0001;
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
Loading

0 comments on commit 95471a0

Please sign in to comment.