use bytes::{Buf, BufMut, Bytes, BytesMut};
use tokio_util::codec::{Decoder, Encoder};
use tracing::trace;
use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq)]
pub enum WhatsMinerFrame {
Heartbeat,
Data(Bytes),
}
pub struct WhatsMinerCodec;
impl Decoder for WhatsMinerCodec {
type Item = WhatsMinerFrame;
type Error = Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>> {
if src.len() < 4 {
return Ok(None);
}
let length = u32::from_le_bytes([src[0], src[1], src[2], src[3]]) as usize;
if length == 0 {
src.advance(4);
trace!("Heartbeat frame decoded");
return Ok(Some(WhatsMinerFrame::Heartbeat));
}
if src.len() - 4 < length {
return Ok(None);
}
src.advance(4);
let payload = src.split_to(length).freeze();
trace!(frame_len = length, "Data frame decoded");
Ok(Some(WhatsMinerFrame::Data(payload)))
}
}
impl Encoder<WhatsMinerFrame> for WhatsMinerCodec {
type Error = Error;
fn encode(&mut self, item: WhatsMinerFrame, dst: &mut BytesMut) -> Result<()> {
match item {
WhatsMinerFrame::Heartbeat => dst.put_u32_le(0),
WhatsMinerFrame::Data(payload) => {
let len = u32::try_from(payload.len())
.map_err(|_| Error::ResponseTooLarge(payload.len()))?;
dst.reserve(4 + payload.len());
dst.put_u32_le(len);
dst.extend_from_slice(&payload);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_heartbeat() {
let mut codec = WhatsMinerCodec;
let mut buf = BytesMut::from(&[0x00, 0x00, 0x00, 0x00][..]);
let frame = codec.decode(&mut buf).expect("decode should succeed");
assert_eq!(frame, Some(WhatsMinerFrame::Heartbeat));
assert!(buf.is_empty(), "buffer should be fully consumed");
}
#[test]
fn test_decode_data_frame() {
let mut codec = WhatsMinerCodec;
let payload = r#"{"cmd":"get_dev_detail"}"#;
let payload_bytes = payload.as_bytes();
let len = payload_bytes.len() as u32;
let mut buf = BytesMut::with_capacity(4 + payload_bytes.len());
buf.put_u32_le(len);
buf.put_slice(payload_bytes);
let frame = codec.decode(&mut buf).expect("decode should succeed");
assert_eq!(
frame,
Some(WhatsMinerFrame::Data(payload.to_string().into()))
);
assert!(buf.is_empty(), "buffer should be fully consumed");
}
#[test]
fn test_decode_partial_header() {
let mut codec = WhatsMinerCodec;
let mut buf = BytesMut::from(&[0x01, 0x00][..]);
let frame = codec.decode(&mut buf).expect("decode should succeed");
assert_eq!(frame, None);
}
#[test]
fn test_decode_partial_payload() {
let mut codec = WhatsMinerCodec;
let payload = "hello";
let mut buf = BytesMut::with_capacity(6);
buf.put_u32_le(payload.len() as u32);
buf.put_slice(b"hel");
let frame = codec.decode(&mut buf).expect("decode should succeed");
assert_eq!(frame, None);
}
#[test]
fn test_decode_large_payload() {
let mut codec = WhatsMinerCodec;
let payload = vec![b'x'; 8193];
let mut buf = BytesMut::new();
buf.put_u32_le(payload.len() as u32);
buf.extend_from_slice(&payload);
let frame = codec.decode(&mut buf).expect("decode should succeed");
assert_eq!(frame, Some(WhatsMinerFrame::Data(Bytes::from(payload))));
}
#[test]
fn test_encode_heartbeat() {
let mut codec = WhatsMinerCodec;
let mut buf = BytesMut::new();
codec
.encode(WhatsMinerFrame::Heartbeat, &mut buf)
.expect("encode should succeed");
assert_eq!(&buf[..], &[0x00, 0x00, 0x00, 0x00]);
}
#[test]
fn test_encode_data() {
let mut codec = WhatsMinerCodec;
let payload = r#"{"cmd":"get_dev_detail"}"#;
let mut buf = BytesMut::new();
codec
.encode(WhatsMinerFrame::Data(payload.to_string().into()), &mut buf)
.expect("encode should succeed");
let expected_len = payload.len();
let mut expected = BytesMut::with_capacity(4 + payload.len());
expected.put_u32_le(expected_len as u32);
expected.put_slice(payload.as_bytes());
assert_eq!(buf, expected);
}
#[test]
fn test_roundtrip() {
let mut encode_codec = WhatsMinerCodec;
let mut decode_codec = WhatsMinerCodec;
let mut buf = BytesMut::new();
encode_codec
.encode(WhatsMinerFrame::Heartbeat, &mut buf)
.expect("encode heartbeat");
let frame = decode_codec
.decode(&mut buf)
.expect("decode heartbeat")
.expect("should produce a frame");
assert_eq!(frame, WhatsMinerFrame::Heartbeat);
let payload = r#"{"STATUS":"S","ip":"192.168.1.100"}"#;
let mut buf = BytesMut::new();
encode_codec
.encode(WhatsMinerFrame::Data(payload.to_string().into()), &mut buf)
.expect("encode data");
let frame = decode_codec
.decode(&mut buf)
.expect("decode data")
.expect("should produce a frame");
assert_eq!(frame, WhatsMinerFrame::Data(payload.to_string().into()));
}
#[test]
fn test_split_frame() {
let mut codec = WhatsMinerCodec;
let mut buf = BytesMut::new();
buf.extend_from_slice(&[5, 0]);
assert_eq!(codec.decode(&mut buf).unwrap(), None);
buf.extend_from_slice(&[0, 0]);
assert_eq!(codec.decode(&mut buf).unwrap(), None);
buf.extend_from_slice(b"hel");
assert_eq!(codec.decode(&mut buf).unwrap(), None);
buf.extend_from_slice(b"lo");
let frame = codec.decode(&mut buf).unwrap();
assert_eq!(
frame,
Some(WhatsMinerFrame::Data(Bytes::from_static(b"hello")))
);
assert!(buf.is_empty());
}
#[test]
fn test_multiple_frames_in_one_buffer() {
let mut codec = WhatsMinerCodec;
let mut buf = BytesMut::new();
buf.put_u32_le(0);
buf.put_u32_le(5);
buf.put_slice(b"first");
buf.put_u32_le(0);
buf.put_u32_le(6);
buf.put_slice(b"second");
assert_eq!(
codec.decode(&mut buf).unwrap(),
Some(WhatsMinerFrame::Heartbeat)
);
assert_eq!(
codec.decode(&mut buf).unwrap(),
Some(WhatsMinerFrame::Data(Bytes::from_static(b"first")))
);
assert_eq!(
codec.decode(&mut buf).unwrap(),
Some(WhatsMinerFrame::Heartbeat)
);
assert_eq!(
codec.decode(&mut buf).unwrap(),
Some(WhatsMinerFrame::Data(Bytes::from_static(b"second")))
);
assert_eq!(codec.decode(&mut buf).unwrap(), None);
assert!(buf.is_empty());
}
#[test]
fn test_trailing_data_preservation() {
let mut codec = WhatsMinerCodec;
let mut buf = BytesMut::new();
buf.put_u32_le(4);
buf.put_slice(b"ping");
buf.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
let frame = codec.decode(&mut buf).unwrap();
assert_eq!(
frame,
Some(WhatsMinerFrame::Data(Bytes::from_static(b"ping")))
);
assert_eq!(&buf[..], &[0xAA, 0xBB, 0xCC]);
}
#[test]
fn test_no_allocation_from_length_only() {
let mut codec = WhatsMinerCodec;
let mut buf = BytesMut::with_capacity(16);
let initial_cap = buf.capacity();
buf.put_u32_le(1_000_000_000);
let res = codec.decode(&mut buf).unwrap();
assert_eq!(res, None);
assert_eq!(buf.capacity(), initial_cap);
}
}