framing_sv2 9.0.0

Sv2 frames
Documentation
//! # Sv2 Frame Header
//!
//! Defines the [`crate::header::Header`] structure used in the framing of Sv2 messages.
//!
//! Each [`crate::framing::MessageFrame`] 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;
use alloc::vec::Vec;
use binary_sv2::{Deserialize, Serialize, U24};
use core::convert::TryInto;

use crate::SV2_FRAME_HEADER_SIZE;

/// 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
    // (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.  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.  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. 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 = SV2_FRAME_HEADER_SIZE;
    const CHANNEL_MSG_MASK: u16 = 0b1000_0000_0000_0000;

    /// Construct a [`Header`] from raw bytes
    #[inline]
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
        if bytes.len() < Self::SIZE {
            return Err(Error::UnexpectedHeaderLength(bytes.len()));
        };
        let extension_type = u16::from_le_bytes([bytes[0], bytes[1]]);
        let msg_type = bytes[2];
        let msg_length: U24 = u32::from_le_bytes([bytes[3], bytes[4], bytes[5], 0]).try_into()?;
        Ok(Self {
            extension_type,
            msg_type,
            msg_length,
        })
    }

    /// Writes the header into the first [`Header::SIZE`] bytes of `dst`.
    ///
    /// The mirror of [`Header::from_bytes`]. Going through the derived `Serialize` instead costs
    /// a `Vec` allocation per frame to emit six bytes.
    #[inline]
    pub fn write_into(&self, dst: &mut [u8]) -> Result<(), Error> {
        if dst.len() < Self::SIZE {
            return Err(Error::UnexpectedHeaderLength(dst.len()));
        }
        dst[0..2].copy_from_slice(&self.extension_type.to_le_bytes());
        dst[2] = self.msg_type;
        let msg_length: u32 = self.msg_length.into();
        dst[3..6].copy_from_slice(&msg_length.to_le_bytes()[..3]);
        Ok(())
    }

    /// Length of the payload the header declares, not including the header itself.
    #[inline]
    pub fn payload_length(&self) -> usize {
        let inner: u32 = self.msg_length.into();
        inner as usize
    }

    // Construct a [`Header`] from payload length, type and extension type.
    #[inline]
    pub(crate) fn from_len(
        msg_length: usize,
        msg_type: u8,
        extension_type: u16,
    ) -> Result<Header, Error> {
        let len = u32::try_from(msg_length)
            .ok()
            .and_then(|len| U24::try_from(len).ok())
            .ok_or(Error::PayloadTooLong(msg_length))?;
        Ok(Self {
            extension_type,
            msg_type,
            msg_length: len,
        })
    }

    /// 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
    }

    /// Get the [`Header`] extension type without the channel message bit (i.e. bit 15).
    pub fn ext_type_without_channel_msg(&self) -> u16 {
        self.extension_type & !Self::CHANNEL_MSG_MASK
    }

    /// 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 {
        self.extension_type & Self::CHANNEL_MSG_MASK != 0
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::SV2_FRAME_MAX_PAYLOAD_SIZE;
    use alloc::vec;
    use quickcheck::{Arbitrary, Gen};
    use quickcheck_macros::quickcheck;

    #[test]
    fn test_header_from_bytes() {
        let bytes = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
        let header = Header::from_bytes(&bytes).unwrap();
        assert_eq!(header.extension_type, 0x0201);
        assert_eq!(header.msg_type, 0x03);
        assert_eq!(header.msg_length, 0x060504_u32.try_into().unwrap());
    }

    #[test]
    fn test_header_from_len() {
        let header = Header::from_len(0x1234, 0x56, 0x789a).unwrap();
        assert_eq!(header.extension_type, 0x789a);
        assert_eq!(header.msg_type, 0x56);
        assert_eq!(header.msg_length, 0x1234_u32.try_into().unwrap());

        let extension_type = 0;
        let msg_type = 0x1;
        let msg_length = 0x1234_u32;
        let header = Header::from_len(msg_length as usize, msg_type, extension_type).unwrap();
        assert_eq!(header.extension_type, 0);
        assert_eq!(header.msg_type, 0x1);
        assert_eq!(header.msg_length, 0x1234_u32.try_into().unwrap());
    }

    #[derive(Debug, Clone)]
    struct ValidU24(u32);

    impl Arbitrary for ValidU24 {
        fn arbitrary(g: &mut Gen) -> Self {
            ValidU24(u32::arbitrary(g) % (SV2_FRAME_MAX_PAYLOAD_SIZE as u32 + 1))
        }
    }

    #[quickcheck]
    fn prop_header_serialization_roundtrip(
        msg_length: ValidU24,
        msg_type: u8,
        extension_type: u16,
    ) {
        let header = Header::from_len(msg_length.0 as usize, msg_type, extension_type).unwrap();
        let mut bytes = vec![0u8; SV2_FRAME_HEADER_SIZE];
        if binary_sv2::to_writer(header, &mut bytes[..]).is_err() {
            return;
        }

        let deserialized = Header::from_bytes(&bytes).expect("Failed to deserialize header");
        assert_eq!(
            deserialized.msg_type(),
            msg_type,
            "Message type mismatch after roundtrip"
        );
        assert_eq!(
            deserialized.ext_type(),
            extension_type,
            "Extension type mismatch after roundtrip"
        );

        let len: u32 = deserialized.msg_length.into();
        assert_eq!(len, msg_length.0, "Message length mismatch after roundtrip");
    }

    #[quickcheck]
    fn prop_header_from_bytes_size_requirement(bytes: Vec<u8>) {
        let result = Header::from_bytes(&bytes);

        if bytes.len() < SV2_FRAME_HEADER_SIZE {
            assert!(
                matches!(result, Err(Error::UnexpectedHeaderLength(_))),
                "Expected UnexpectedHeaderLength error for buffer with {} bytes, got {:?}",
                bytes.len(),
                result
            );
        } else {
            assert!(
                result.is_ok() || !matches!(result, Err(Error::UnexpectedHeaderLength(_))),
                "Got unexpected UnexpectedHeaderLength error for sufficient buffer size"
            );
        }
    }

    #[quickcheck]
    fn prop_header_channel_msg_bit(extension_type: u16, channel_msg: bool) {
        let msg_length = 100u32;
        let msg_type = 0x01u8;

        let adjusted_extension_type = if channel_msg {
            extension_type | 0b1000_0000_0000_0000
        } else {
            extension_type & 0b0111_1111_1111_1111
        };

        let header =
            Header::from_len(msg_length as usize, msg_type, adjusted_extension_type).unwrap();

        assert_eq!(
            header.channel_msg(),
            channel_msg,
            "channel_msg() should return {} for extension_type 0x{:04X} with MSB {}",
            channel_msg,
            adjusted_extension_type,
            if channel_msg { "set" } else { "unset" }
        );
    }

    #[quickcheck]
    fn prop_header_ext_type_without_channel_msg(extension_type: u16) {
        let msg_length = 100u32;
        let msg_type = 0x01u8;

        let header = Header::from_len(msg_length as usize, msg_type, extension_type).unwrap();

        let without_channel = header.ext_type_without_channel_msg();
        let expected = extension_type & 0b0111_1111_1111_1111;
        assert_eq!(
            without_channel, expected,
            "ext_type_without_channel_msg() should clear MSB: got 0x{:04X}, expected 0x{:04X}",
            without_channel, expected
        );
    }

    #[quickcheck]
    fn prop_header_len_consistency(msg_length: ValidU24) {
        let header = Header::from_len(msg_length.0 as usize, 0x01, 0x0000).unwrap();

        assert_eq!(
            header.payload_length(),
            msg_length.0 as usize,
            "Header len() should match the msg_length used to create it"
        );
    }

    #[quickcheck]
    fn prop_header_size_is_constant() {
        assert_eq!(
            Header::SIZE,
            SV2_FRAME_HEADER_SIZE,
            "Header::SIZE should equal SV2_FRAME_HEADER_SIZE"
        );
        assert_eq!(
            SV2_FRAME_HEADER_SIZE, 6,
            "SV2_FRAME_HEADER_SIZE should be 6 bytes"
        );
    }
}