use crate::DCPError;
#[allow(non_snake_case)]
pub mod ChunkFlags {
pub const FIRST: u8 = 0b0001;
pub const CONTINUE: u8 = 0b0010;
pub const LAST: u8 = 0b0100;
pub const ERROR: u8 = 0b1000;
}
const ALL_CHUNK_FLAGS: u8 =
ChunkFlags::FIRST | ChunkFlags::CONTINUE | ChunkFlags::LAST | ChunkFlags::ERROR;
fn valid_chunk_flags(flags: u8) -> bool {
if flags == 0 || flags & !ALL_CHUNK_FLAGS != 0 {
return false;
}
if flags & ChunkFlags::ERROR != 0 {
return flags == ChunkFlags::ERROR;
}
matches!(
flags,
ChunkFlags::FIRST | ChunkFlags::CONTINUE | ChunkFlags::LAST
) || flags == (ChunkFlags::FIRST | ChunkFlags::LAST)
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StreamChunk {
pub sequence: u32,
pub flags: u8,
pub len: u16,
}
impl StreamChunk {
pub const SIZE: usize = 7;
pub fn new(sequence: u32, flags: u8, len: u16) -> Self {
Self {
sequence,
flags,
len,
}
}
pub fn first(sequence: u32, len: u16) -> Self {
Self::new(sequence, ChunkFlags::FIRST, len)
}
pub fn continuation(sequence: u32, len: u16) -> Self {
Self::new(sequence, ChunkFlags::CONTINUE, len)
}
pub fn last(sequence: u32, len: u16) -> Self {
Self::new(sequence, ChunkFlags::LAST, len)
}
pub fn error(sequence: u32, len: u16) -> Self {
Self::new(sequence, ChunkFlags::ERROR, len)
}
#[inline(always)]
pub fn from_bytes(bytes: &[u8]) -> Result<&Self, DCPError> {
if bytes.len() < Self::SIZE {
return Err(DCPError::InsufficientData);
}
if !valid_chunk_flags(bytes[4]) {
return Err(DCPError::ValidationFailed);
}
Ok(unsafe { &*(bytes.as_ptr() as *const Self) })
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
}
#[inline(always)]
pub fn is_first(&self) -> bool {
self.flags & ChunkFlags::FIRST != 0
}
#[inline(always)]
pub fn is_continuation(&self) -> bool {
self.flags & ChunkFlags::CONTINUE != 0
}
#[inline(always)]
pub fn is_last(&self) -> bool {
self.flags & ChunkFlags::LAST != 0
}
#[inline(always)]
pub fn is_error(&self) -> bool {
self.flags & ChunkFlags::ERROR != 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chunk_size() {
assert_eq!(std::mem::size_of::<StreamChunk>(), 7);
}
#[test]
fn test_chunk_round_trip() {
let chunk = StreamChunk::new(12345, ChunkFlags::FIRST | ChunkFlags::LAST, 1024);
let bytes = chunk.as_bytes();
let parsed = StreamChunk::from_bytes(bytes).unwrap();
let sequence = parsed.sequence;
let flags = parsed.flags;
let len = parsed.len;
assert_eq!(sequence, 12345);
assert_eq!(flags, ChunkFlags::FIRST | ChunkFlags::LAST);
assert_eq!(len, 1024);
}
#[test]
fn test_flag_helpers() {
let first = StreamChunk::first(0, 100);
assert!(first.is_first());
assert!(!first.is_last());
let last = StreamChunk::last(1, 50);
assert!(last.is_last());
assert!(!last.is_first());
let error = StreamChunk::error(2, 0);
assert!(error.is_error());
}
#[test]
fn test_insufficient_data() {
let bytes = [0u8; 4];
assert_eq!(
StreamChunk::from_bytes(&bytes),
Err(DCPError::InsufficientData)
);
}
}