use crate::{header::Header, Error};
use alloc::vec::Vec;
use binary_sv2::{to_writer, GetSize, Serialize};
use core::convert::TryFrom;
#[cfg(not(feature = "with_buffer_pool"))]
type Slice = Vec<u8>;
#[cfg(feature = "with_buffer_pool")]
type Slice = buffer_sv2::Slice;
#[derive(Debug)]
pub enum Frame<T, B> {
HandShake(HandShakeFrame),
Sv2(Sv2Frame<T, B>),
}
impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Frame<T, B> {
pub fn encoded_length(&self) -> usize {
match &self {
Self::HandShake(frame) => frame.encoded_length(),
Self::Sv2(frame) => frame.encoded_length(),
}
}
}
impl<T, B> From<HandShakeFrame> for Frame<T, B> {
fn from(v: HandShakeFrame) -> Self {
Self::HandShake(v)
}
}
impl<T, B> From<Sv2Frame<T, B>> for Frame<T, B> {
fn from(v: Sv2Frame<T, B>) -> Self {
Self::Sv2(v)
}
}
#[derive(Debug, Clone)]
pub struct Sv2Frame<T, B> {
header: Header,
payload: Option<T>,
serialized: Option<B>,
}
impl<T: Serialize + GetSize, B: AsMut<[u8]> + AsRef<[u8]>> Sv2Frame<T, B> {
#[inline]
pub fn serialize(self, dst: &mut [u8]) -> Result<(), Error> {
if let Some(mut serialized) = self.serialized {
dst.swap_with_slice(serialized.as_mut());
Ok(())
} else if let Some(payload) = self.payload {
to_writer(self.header, dst).map_err(Error::BinarySv2Error)?;
to_writer(payload, &mut dst[Header::SIZE..]).map_err(Error::BinarySv2Error)?;
Ok(())
} else {
panic!("Impossible state")
}
}
pub fn payload(&mut self) -> &mut [u8] {
if let Some(serialized) = self.serialized.as_mut() {
&mut serialized.as_mut()[Header::SIZE..]
} else {
panic!("Sv2Frame is not yet serialized.")
}
}
pub fn get_header(&self) -> Option<crate::header::Header> {
Some(self.header)
}
#[inline]
pub fn from_bytes(mut bytes: B) -> Result<Self, isize> {
let hint = Self::size_hint(bytes.as_mut());
if hint == 0 {
Ok(Self::from_bytes_unchecked(bytes))
} else {
Err(hint)
}
}
#[inline]
pub fn from_bytes_unchecked(mut bytes: B) -> Self {
let header = Header::from_bytes(bytes.as_mut()).expect("Invalid header");
Self {
header,
payload: None,
serialized: Some(bytes),
}
}
#[inline]
pub fn size_hint(bytes: &[u8]) -> isize {
match Header::from_bytes(bytes) {
Err(_) => {
(Header::SIZE - bytes.len()) as isize
}
Ok(header) => {
if bytes.len() - Header::SIZE == header.len() {
0
} else {
(bytes.len() - Header::SIZE) as isize + header.len() as isize
}
}
}
}
#[inline]
pub fn encoded_length(&self) -> usize {
if let Some(serialized) = self.serialized.as_ref() {
serialized.as_ref().len()
} else if let Some(payload) = self.payload.as_ref() {
payload.get_size() + Header::SIZE
} else {
panic!("Impossible state")
}
}
pub fn from_message(
message: T,
message_type: u8,
extension_type: u16,
channel_msg: bool,
) -> Option<Self> {
let extension_type = update_extension_type(extension_type, channel_msg);
let len = message.get_size() as u32;
Header::from_len(len, message_type, extension_type).map(|header| Self {
header,
payload: Some(message),
serialized: None,
})
}
}
impl<A, B> Sv2Frame<A, B> {
pub fn map<C>(self, fun: fn(A) -> C) -> Sv2Frame<C, B> {
let serialized = self.serialized;
let header = self.header;
let payload = self.payload.map(fun);
Sv2Frame {
header,
payload,
serialized,
}
}
}
impl<T, B> TryFrom<Frame<T, B>> for Sv2Frame<T, B> {
type Error = Error;
fn try_from(v: Frame<T, B>) -> Result<Self, Error> {
match v {
Frame::Sv2(frame) => Ok(frame),
Frame::HandShake(_) => Err(Error::ExpectedSv2Frame),
}
}
}
#[derive(Debug)]
pub struct HandShakeFrame {
payload: Slice,
}
impl HandShakeFrame {
pub fn get_payload_when_handshaking(&self) -> Vec<u8> {
self.payload[0..].to_vec()
}
pub fn from_bytes(bytes: Slice) -> Result<Self, isize> {
Ok(Self::from_bytes_unchecked(bytes))
}
#[inline]
pub fn from_bytes_unchecked(bytes: Slice) -> Self {
Self { payload: bytes }
}
#[inline]
fn encoded_length(&self) -> usize {
self.payload.len()
}
}
impl<T, B> TryFrom<Frame<T, B>> for HandShakeFrame {
type Error = Error;
fn try_from(v: Frame<T, B>) -> Result<Self, Error> {
match v {
Frame::HandShake(frame) => Ok(frame),
Frame::Sv2(_) => Err(Error::ExpectedHandshakeFrame),
}
}
}
#[allow(clippy::useless_conversion)]
pub fn handshake_message_to_frame<T: AsRef<[u8]>>(message: T) -> HandShakeFrame {
let mut payload = Vec::new();
payload.extend_from_slice(message.as_ref());
HandShakeFrame {
payload: payload.into(),
}
}
fn update_extension_type(extension_type: u16, channel_msg: bool) -> u16 {
if channel_msg {
let mask = 0b1000_0000_0000_0000;
extension_type | mask
} else {
let mask = 0b0111_1111_1111_1111;
extension_type & mask
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use binary_sv2::Serialize;
use quickcheck::{Arbitrary, Gen};
use quickcheck_macros::quickcheck;
#[derive(Serialize)]
struct T {}
#[test]
fn test_size_hint() {
let h = Sv2Frame::<T, Vec<u8>>::size_hint(&[0, 128, 30, 46, 0, 0][..]);
assert!(h == 46);
}
#[derive(Debug, Clone)]
struct ValidU24(u32);
impl Arbitrary for ValidU24 {
fn arbitrary(g: &mut Gen) -> Self {
ValidU24(u32::arbitrary(g) % 16_777_216)
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
struct TestMessage {
data: Vec<u8>,
}
impl Arbitrary for TestMessage {
fn arbitrary(g: &mut Gen) -> Self {
let size = usize::arbitrary(g) % 256;
let data: Vec<u8> = (0..size).map(|_| u8::arbitrary(g)).collect();
TestMessage {
data: data.try_into().unwrap(),
}
}
}
#[quickcheck]
fn prop_sv2frame_from_message_size_limit(msg: TestMessage) {
let msg_type = 0x01u8;
let extension_type = 0x0000u16;
let frame = Sv2Frame::<TestMessage, Vec<u8>>::from_message(
msg.clone(),
msg_type,
extension_type,
false,
);
if msg.get_size() < 16_777_216 {
assert!(
frame.is_some(),
"Frame creation should succeed for message size {} < U24_MAX",
msg.get_size()
);
} else {
assert!(
frame.is_none(),
"Frame creation should fail for message size {} >= U24_MAX",
msg.get_size()
);
}
}
#[quickcheck]
fn prop_sv2frame_encoded_length_consistency(msg: TestMessage) {
let msg_type = 0x01u8;
let extension_type = 0x0000u16;
let frame = Sv2Frame::<TestMessage, Vec<u8>>::from_message(
msg.clone(),
msg_type,
extension_type,
false,
)
.unwrap();
let encoded_len = frame.encoded_length();
let expected_len = msg.get_size() + Header::SIZE;
assert_eq!(
encoded_len,
expected_len,
"Frame encoded_length() should be msg_size({}) + header_size({}), got {}",
msg.get_size(),
Header::SIZE,
encoded_len
);
}
#[quickcheck]
fn prop_sv2frame_serialization_roundtrip_small(data: Vec<u8>) {
let data: Vec<u8> = data.iter().take(1000).copied().collect();
let msg = TestMessage { data };
let msg_type = 0x01u8;
let extension_type = 0x0000u16;
let frame = Sv2Frame::<TestMessage, Vec<u8>>::from_message(
msg.clone(),
msg_type,
extension_type,
false,
)
.unwrap();
let mut buffer = vec![0u8; frame.encoded_length()];
frame
.serialize(&mut buffer)
.expect("Serialization should succeed");
let deserialized = Sv2Frame::<TestMessage, Vec<u8>>::from_bytes(buffer)
.expect("Deserialization should succeed");
let header = deserialized
.get_header()
.expect("Sv2Frame should always have header");
assert_eq!(
header.msg_type(),
msg_type,
"Message type should match after roundtrip"
);
assert_eq!(
header.ext_type_without_channel_msg(),
extension_type,
"Extension type should match after roundtrip"
);
assert_eq!(
header.len(),
msg.get_size(),
"Payload length should match after roundtrip"
);
}
#[quickcheck]
fn prop_sv2frame_size_hint_exact_match(msg_length: ValidU24) {
let msg_type = 0x01u8;
let extension_type = 0x0000u16;
let header = Header::from_len(msg_length.0, msg_type, extension_type).unwrap();
let mut bytes = vec![0u8; Header::SIZE + msg_length.0 as usize];
binary_sv2::to_writer(header, &mut bytes[..Header::SIZE]).unwrap();
let hint = Sv2Frame::<TestMessage, Vec<u8>>::size_hint(&bytes);
assert_eq!(
hint, 0,
"size_hint should return 0 when bytes match expected frame size exactly"
);
}
#[quickcheck]
fn prop_sv2frame_size_hint_insufficient_header(bytes: Vec<u8>) {
let bytes: Vec<u8> = bytes.iter().take(Header::SIZE - 1).copied().collect();
let hint = Sv2Frame::<TestMessage, Vec<u8>>::size_hint(&bytes);
let expected = (Header::SIZE - bytes.len()) as isize;
assert!(
hint > 0,
"size_hint should be positive when header is incomplete"
);
assert_eq!(
hint, expected,
"size_hint should return missing bytes count: expected {}, got {}",
expected, hint
);
}
#[quickcheck]
fn prop_sv2frame_channel_msg_flag(msg: TestMessage, channel_msg: bool) {
let msg_type = 0x01u8;
let extension_type = 0x0ABCu16;
if msg.get_size() >= 16_777_216 {
return;
}
let frame = Sv2Frame::<TestMessage, Vec<u8>>::from_message(
msg,
msg_type,
extension_type,
channel_msg,
)
.unwrap();
let header = frame
.get_header()
.expect("Sv2Frame should always have header");
assert_eq!(
header.channel_msg(),
channel_msg,
"Frame channel_msg flag should be {} as specified in from_message",
channel_msg
);
}
#[quickcheck]
fn prop_sv2frame_get_header_always_some(msg: TestMessage) {
let msg_type = 0x01u8;
let extension_type = 0x0000u16;
let frame =
Sv2Frame::<TestMessage, Vec<u8>>::from_message(msg, msg_type, extension_type, false)
.unwrap();
assert!(
frame.get_header().is_some(),
"Sv2Frame::get_header() should always return Some"
);
}
#[quickcheck]
fn prop_handshake_frame_roundtrip(payload: Vec<u8>) {
let payload: Vec<u8> = payload.iter().take(1000).copied().collect();
let frame = handshake_message_to_frame(&payload);
let recovered = frame.get_payload_when_handshaking();
assert_eq!(
recovered,
payload,
"HandShakeFrame roundtrip should preserve payload exactly (size: {})",
payload.len()
);
}
#[quickcheck]
fn prop_handshake_frame_encoded_length(payload: Vec<u8>) {
let payload: Vec<u8> = payload.iter().take(1000).copied().collect();
let expected_len = payload.len();
let frame = handshake_message_to_frame(&payload);
assert_eq!(
frame.encoded_length(),
expected_len,
"HandShakeFrame encoded_length should equal payload length"
);
}
#[quickcheck]
fn prop_handshake_frame_from_bytes(payload: Vec<u8>) {
let payload: Vec<u8> = payload.iter().take(1000).copied().collect();
let frame = HandShakeFrame::from_bytes(payload.clone().into())
.expect("HandShakeFrame::from_bytes should succeed for any valid payload");
let recovered = frame.get_payload_when_handshaking();
assert_eq!(
recovered,
payload,
"Payload should be preserved through from_bytes (size: {})",
payload.len()
);
}
#[quickcheck]
fn prop_update_extension_type_channel_msg_set(extension_type: u16) {
let result = update_extension_type(extension_type, true);
assert_ne!(
result & 0b1000_0000_0000_0000,
0,
"update_extension_type with channel_msg=true should set MSB: input=0x{:04X}, output=0x{:04X}",
extension_type,
result
);
}
#[quickcheck]
fn prop_update_extension_type_channel_msg_unset(extension_type: u16) {
let result = update_extension_type(extension_type, false);
assert_eq!(
result & 0b1000_0000_0000_0000,
0,
"update_extension_type with channel_msg=false should clear MSB: input=0x{:04X}, output=0x{:04X}",
extension_type,
result
);
}
#[quickcheck]
fn prop_update_extension_type_preserves_lower_bits_when_set(extension_type: u16) {
let result = update_extension_type(extension_type, true);
let lower_bits = extension_type & 0b0111_1111_1111_1111;
let result_lower_bits = result & 0b0111_1111_1111_1111;
assert_eq!(
lower_bits, result_lower_bits,
"update_extension_type should preserve lower 15 bits when setting MSB: input=0x{:04X}, expected_lower=0x{:04X}, got_lower=0x{:04X}",
extension_type, lower_bits, result_lower_bits
);
}
#[quickcheck]
fn prop_update_extension_type_preserves_lower_bits_when_unset(extension_type: u16) {
let result = update_extension_type(extension_type, false);
let lower_bits = extension_type & 0b0111_1111_1111_1111;
assert_eq!(
result, lower_bits,
"update_extension_type with channel_msg=false should return only lower 15 bits: input=0x{:04X}, expected=0x{:04X}, got=0x{:04X}",
extension_type, lower_bits, result
);
}
#[ignore = "size_hint semantics are broken (see https://github.com/stratum-mining/stratum/issues/2086)"]
#[quickcheck]
fn prop_size_hint_truncated_payload(msg_length: ValidU24, cut: u16) {
let msg_type = 0x01u8;
let ext = 0u16;
let header = Header::from_len(msg_length.0, msg_type, ext).unwrap();
let payload_len = msg_length.0 as usize;
if payload_len == 0 {
return;
}
let missing = (cut as usize % payload_len) + 1;
let actual_payload = payload_len - missing;
let mut bytes = vec![0u8; Header::SIZE + actual_payload];
binary_sv2::to_writer(header, &mut bytes[..Header::SIZE]).unwrap();
let hint = Sv2Frame::<TestMessage, Vec<u8>>::size_hint(&bytes);
assert!(
hint < 0,
"size_hint should be negative when payload is truncated"
);
assert_eq!(
hint,
-(missing as isize),
"size_hint should equal missing bytes"
);
}
#[ignore = "size_hint semantics are broken (see https://github.com/stratum-mining/stratum/issues/2086)"]
#[quickcheck]
fn prop_size_hint_extra_bytes(msg_length: ValidU24, extra: u16) {
let msg_type = 0x01u8;
let ext = 0u16;
let header = Header::from_len(msg_length.0, msg_type, ext).unwrap();
let extra = (extra % 64 + 1) as usize;
let mut bytes = vec![0u8; Header::SIZE + msg_length.0 as usize + extra];
binary_sv2::to_writer(header, &mut bytes[..Header::SIZE]).unwrap();
let hint = Sv2Frame::<TestMessage, Vec<u8>>::size_hint(&bytes);
assert!(
hint > 0,
"size_hint should be positive when extra bytes exist"
);
assert_eq!(
hint, extra as isize,
"size_hint should equal number of extra bytes"
);
}
#[ignore = "size_hint semantics are broken (see https://github.com/stratum-mining/stratum/issues/2086)"]
#[quickcheck]
fn prop_size_hint_matches_delta(msg_length: ValidU24, delta: i16) {
let msg_type = 0x01u8;
let ext = 0u16;
let header = Header::from_len(msg_length.0, msg_type, ext).unwrap();
let expected = msg_length.0 as isize;
let actual = (expected + delta as isize).max(0) as usize;
let mut bytes = vec![0u8; Header::SIZE + actual];
binary_sv2::to_writer(header, &mut bytes[..Header::SIZE]).unwrap();
let hint = Sv2Frame::<TestMessage, Vec<u8>>::size_hint(&bytes);
assert_eq!(
hint,
actual as isize - expected,
"size_hint must equal actual - expected payload size"
);
}
#[ignore = "size_hint semantics are broken (see https://github.com/stratum-mining/stratum/issues/2086)"]
#[quickcheck]
fn prop_size_hint_monotonic_growth(msg_length: ValidU24) {
let header = Header::from_len(msg_length.0, 1, 0).unwrap();
let total = Header::SIZE + msg_length.0 as usize;
let mut full = vec![0u8; total];
binary_sv2::to_writer(header, &mut full[..Header::SIZE]).unwrap();
let mut prev = isize::MIN;
for i in 0..=total {
let hint = Sv2Frame::<TestMessage, Vec<u8>>::size_hint(&full[..i]);
assert!(hint >= prev, "hint should increase as more bytes arrive");
prev = hint;
}
}
}