Skip to content

Commit

Permalink
add framing_sv2 docs
Browse files Browse the repository at this point in the history
  • Loading branch information
plebhash committed Apr 28, 2024
1 parent 60465ad commit 2af7386
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 59 deletions.
113 changes: 71 additions & 42 deletions protocols/v2/framing-sv2/src/framing2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type Slice = Vec<u8>;
type Slice = buffer_sv2::Slice;

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`
pub fn map<C>(self, fun: fn(A) -> C) -> Sv2Frame<C, B> {
let serialized = self.serialized;
let header = self.header;
Expand All @@ -31,31 +33,35 @@ pub trait Frame<'a, T: Serialize + GetSize>: Sized {
type Buffer: AsMut<[u8]>;
type Deserialized;

/// Serialize the frame into dst if the frame is already serialized it just swap dst with
/// itself
/// Write the serialized `Frame` into `dst`.
fn serialize(self, dst: &mut [u8]) -> Result<(), Error>;

//fn deserialize(&'a mut self) -> Result<Self::Deserialized, serde_sv2::Error>;

/// Get the payload
fn payload(&'a mut self) -> &'a mut [u8];

/// If is an Sv2 frame return the Some(header) if it is a noise frame return None
/// `Sv2Frame` returns `Some(self.header)`, `NoiseFrame` return `None`.
fn get_header(&self) -> Option<crate::header::Header>;

/// Try to build an Frame frame from raw bytes.
/// It return the frame or the number of the bytes needed to complete the frame
/// The resulting frame is just a header plus a payload with the right number of bytes nothing
/// is said about the correctness of the payload
/// Try to build a `Frame` from raw bytes.
/// Returns `Self` on success, or the number of the bytes needed to complete the frame
/// as an error. Nothing is assumed or checked about the correctness of the payload.
fn from_bytes(bytes: Self::Buffer) -> Result<Self, isize>;

fn from_bytes_unchecked(bytes: Self::Buffer) -> Self;

/// Helps to determine if the frame size encoded in a byte array 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 smaller than a Noise Frame header; this value
/// represents how many bytes are missing.
/// - Returns a positive value if the byte slice is longer than expected; this value
/// indicates the surplus of bytes beyond the expected size.
fn size_hint(bytes: &[u8]) -> isize;

/// Returns the size of the `Frame` payload.
fn encoded_length(&self) -> usize;

/// Try to build an Frame frame from a serializable payload.
/// It return a Frame if the size of the payload fit in the frame, if not it return None
/// Try to build a `Frame` from a serializable payload.
/// Returns `Some(Self)` if the size of the payload fits in the frame, `None` otherwise.
fn from_message(
message: T,
message_type: u8,
Expand All @@ -64,11 +70,12 @@ pub trait Frame<'a, T: Serialize + GetSize>: Sized {
) -> Option<Self>;
}

/// Abstraction for a SV2 Frame.
#[derive(Debug, Clone)]
pub struct Sv2Frame<T, B> {
header: Header,
payload: Option<T>,
/// Serializsed header + payload
/// Serialized header + payload
serialized: Option<B>,
}

Expand All @@ -82,20 +89,25 @@ impl<T, B> Default for Sv2Frame<T, B> {
}
}

/// Abstraction for a Noise Frame
#[derive(Debug)]
pub struct NoiseFrame {
payload: Slice,
}

/// todo
pub type HandShakeFrame = NoiseFrame;

/// todo (why unreachable?)
#[cfg(feature = "with_buffer_pool")]
impl<A> From<EitherFrame<A, Vec<u8>>> for Sv2Frame<A, buffer_sv2::Slice> {
fn from(_: EitherFrame<A, Vec<u8>>) -> Self {
unreachable!()
}
}

impl NoiseFrame {
/// Returns payload of `NoiseFrame` as a `Vec<u8>`
pub fn get_payload_when_handshaking(&self) -> Vec<u8> {
self.payload[0..].to_vec()
}
Expand All @@ -105,8 +117,7 @@ impl<'a, T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Frame<'a, T> for
type Buffer = B;
type Deserialized = B;

/// Serialize the frame into dst if the frame is already serialized it just swap dst with
/// itself
/// Write the serialized `Sv2Frame` into `dst`.
#[inline]
fn serialize(self, dst: &mut [u8]) -> Result<(), Error> {
if let Some(mut serialized) = self.serialized {
Expand All @@ -129,30 +140,28 @@ impl<'a, T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Frame<'a, T> for
}
}

// self can be either serialized (it cointain an AsMut<[u8]> with the serialized data or
// deserialized it contain the rust type that represant the Sv2 message. If the type is
// deserialized self.paylos.is_some() is true. To get the serialized payload the inner type
// should be serialized and this function should never be used, cause is intended as a fast
// function that return a reference to an already serialized payload. For that the function
// panic.
/// `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).
fn payload(&'a mut self) -> &'a mut [u8] {
if let Some(serialized) = self.serialized.as_mut() {
&mut serialized.as_mut()[Header::SIZE..]
} else {
// panic here is the expected behaviour
panic!()
panic!("Sv2Frame is not yet serialized.")
}
}

/// If is an Sv2 frame return the Some(header) if it is a noise frame return None
/// `Sv2Frame` always returns `Some(self.header)`.
fn get_header(&self) -> Option<crate::header::Header> {
Some(self.header)
}

/// Try to build a Frame frame from raw bytes.
/// It return the frame or the number of the bytes needed to complete the frame
/// The resulting frame is just a header plus a payload with the right number of bytes nothing
/// is said about the correctness of the payload
/// 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.
#[inline]
fn from_bytes(mut bytes: Self::Buffer) -> Result<Self, isize> {
let hint = Self::size_hint(bytes.as_mut());
Expand All @@ -175,23 +184,34 @@ impl<'a, T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Frame<'a, T> for
}
}

/// 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.
/// - Returns a positive value if the byte slice is longer than expected; this value
/// indicates the surplus of bytes beyond the expected size.
#[inline]
fn size_hint(bytes: &[u8]) -> isize {
match Header::from_bytes(bytes) {
Err(_) => {
// Return incorrect header length
// Returns how many bytes are missing from the expected frame size
(Header::SIZE - bytes.len()) as isize
}
Ok(header) => {
if bytes.len() - Header::SIZE == header.len() {
// expected frame size confirmed
0
} else {
// Returns how many excess bytes are beyond the expected frame size
(bytes.len() - Header::SIZE) as isize + header.len() as isize
}
}
}
}

/// If `Sv2Frame` is serialized, returns the length of `self.serialized`,
/// otherwise, returns the length of `self.pauload`.
#[inline]
fn encoded_length(&self) -> usize {
if let Some(serialized) = self.serialized.as_ref() {
Expand All @@ -204,8 +224,8 @@ impl<'a, T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Frame<'a, T> for
}
}

/// Try to build an Frame frame from a serializable payload.
/// It returns a Frame if the size of the payload fits in the frame, if not it returns None
/// 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.
fn from_message(
message: T,
message_type: u8,
Expand All @@ -232,25 +252,25 @@ impl<'a> Frame<'a, Slice> for NoiseFrame {
type Buffer = Slice;
type Deserialized = &'a mut [u8];

/// Serialize the frame into dst if the frame is already serialized it just swap dst with
/// itself
/// Put the Noise Frame payload into `dst`
#[inline]
fn serialize(mut self, dst: &mut [u8]) -> Result<(), Error> {
dst.swap_with_slice(self.payload.as_mut());
Ok(())
}

/// Get the Noise Frame payload
#[inline]
fn payload(&'a mut self) -> &'a mut [u8] {
&mut self.payload[NoiseHeader::HEADER_SIZE..]
}

/// If is an Sv2 frame return the Some(header) if it is a noise frame return None
/// `NoiseFrame` always returns `None`.
fn get_header(&self) -> Option<crate::header::Header> {
None
}

// For a NoiseFrame from_bytes is the same of from_bytes_unchecked
/// Builds a `NoiseFrame` from raw bytes. Nothing is assumed or checked about the correctness of the payload.
fn from_bytes(bytes: Self::Buffer) -> Result<Self, isize> {
Ok(Self::from_bytes_unchecked(bytes))
}
Expand All @@ -260,6 +280,13 @@ impl<'a> Frame<'a, Slice> for NoiseFrame {
Self { payload: bytes }
}

/// After parsing the expected `NoiseFrame` size from `bytes`, this function helps to determine if this value
/// 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 smaller than a Noise Frame header; this value
/// represents how many bytes are missing.
/// - Returns a positive value if the byte slice is longer than expected; this value
/// indicates the surplus of bytes beyond the expected size.
#[inline]
fn size_hint(bytes: &[u8]) -> isize {
if bytes.len() < NoiseHeader::HEADER_SIZE {
Expand All @@ -276,15 +303,16 @@ impl<'a> Frame<'a, Slice> for NoiseFrame {
}
}

/// Returns the size of the `NoiseFrame` payload.
#[inline]
fn encoded_length(&self) -> usize {
self.payload.len()
}

/// Try to build a `Frame` frame from a serializable payload.
/// It returns a Frame if the size of the payload fits in the frame, if not it returns None
/// Inneficient should be used only to build `HandShakeFrames`
/// TODO check if is used only to build `HandShakeFrames`
/// Tries to build a `NoiseFrame` frame from a byte slice.
/// Returns a `NoiseFrame` if the size of the payload fits in the frame, `None` otherwise.
/// This is quite inefficient, and should be used only to build `HandShakeFrames`
// TODO check if is used only to build `HandShakeFrames`
#[allow(clippy::useless_conversion)]
fn from_message(
message: Slice,
Expand All @@ -302,6 +330,7 @@ impl<'a> Frame<'a, Slice> for NoiseFrame {
}
}

/// 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 @@ -311,6 +340,10 @@ 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.
fn update_extension_type(extension_type: u16, channel_msg: bool) -> u16 {
if channel_msg {
let mask = 0b1000_0000_0000_0000;
Expand All @@ -321,11 +354,7 @@ fn update_extension_type(extension_type: u16, channel_msg: bool) -> u16 {
}
}

/// A frame can be either
/// 1: Sv2Frame
/// 2: NoiseFrame
/// 3: HandashakeFrame
///
/// todo
#[derive(Debug)]
pub enum EitherFrame<T, B> {
HandShake(HandShakeFrame),
Expand Down
8 changes: 8 additions & 0 deletions protocols/v2/framing-sv2/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use binary_sv2::{Deserialize, Serialize, U24};
use const_sv2::{AEAD_MAC_LEN, SV2_FRAME_CHUNK_SIZE};
use core::convert::TryInto;

/// Abstraction for a SV2 Frame Header.
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
pub struct Header {
extension_type: u16, // TODO use specific type?
Expand All @@ -32,6 +33,7 @@ impl Header {

pub const SIZE: usize = const_sv2::SV2_FRAME_HEADER_SIZE;

/// Construct a `Header` from ray bytes
#[inline]
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < Self::SIZE {
Expand All @@ -52,13 +54,15 @@ impl Header {
})
}

/// Get the message length
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> usize {
let inner: u32 = self.msg_length.into();
inner as usize
}

/// Construct a `Header` from message length, type and extension type.
#[inline]
pub fn from_len(len: u32, message_type: u8, extension_type: u16) -> Option<Header> {
Some(Self {
Expand All @@ -68,19 +72,23 @@ impl Header {
})
}

/// Get the `Header` message type.
pub fn msg_type(&self) -> u8 {
self.msg_type
}

/// Get the `Header` extension type.
pub fn ext_type(&self) -> u16 {
self.extension_type
}

/// Check if `Header` represents a channel message
pub fn channel_msg(&self) -> bool {
let mask = 0b0000_0000_0000_0001;
self.extension_type & mask == self.extension_type
}

/// Calculate the length of the encrypted `Header`
pub fn encrypted_len(&self) -> usize {
let len = self.len();
let mut chunks = len / (SV2_FRAME_CHUNK_SIZE - AEAD_MAC_LEN);
Expand Down
40 changes: 23 additions & 17 deletions protocols/v2/framing-sv2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
//! 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.
//!
//! This crate provides primitives for framing of SV2 binary messages.
//!
//! The message framing is outlined below:
//!
//! | Protocol Type | Byte Length | Description |
//! |----------------|-------------|-------------|
//! | `extension_type` | `U16` | Unique identifier of the extension describing this protocol message. <br><br> Most significant bit (i.e.bit `15`, `0`-indexed, aka `channel_msg`) indicates a message which is specific to a channel, whereas if the most significant bit is unset, the message is to be interpreted by the immediate receiving device. <br><br> Note that the `channel_msg` bit is ignored in the extension lookup, i.e.an `extension_type` of `0x8ABC` is for the same "extension" as `0x0ABC`. <br><br> If the `channel_msg` bit is set, the first four bytes of the payload field is a `U32` representing the `channel_id` this message is destined for (these bytes are repeated in the message framing descriptions below). <br><br> Note that for the Job Declaration and Template Distribution Protocols the `channel_msg` bit is always unset. |
//! | `msg_type` | `U8` | Unique identifier of the extension describing this protocol message. |
//! | `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
//! 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.

#![no_std]
extern crate alloc;

///
/// Sv2 messages are framed as
/// ```txt
/// extension type: u16
/// msg type: u8
/// msg length: u24
/// payload: [u8; msg length]
/// ```
///
/// Sv2 messages can be encapsulated in noise messages, noise messages are framed as:
///
/// ```txt
/// msg length: u16
/// payload: [u8; msg length]
/// ```
///
///
/// SV2 framing types
pub mod framing2;

/// SV2 framing errors
pub mod error;

/// SV2 framing header
pub mod header;
pub use error::Error;

0 comments on commit 2af7386

Please sign in to comment.