1use crate::DCPError;
4
5#[allow(non_snake_case)]
7pub mod ChunkFlags {
8 pub const FIRST: u8 = 0b0001;
9 pub const CONTINUE: u8 = 0b0010;
10 pub const LAST: u8 = 0b0100;
11 pub const ERROR: u8 = 0b1000;
12}
13
14const ALL_CHUNK_FLAGS: u8 =
15 ChunkFlags::FIRST | ChunkFlags::CONTINUE | ChunkFlags::LAST | ChunkFlags::ERROR;
16
17fn valid_chunk_flags(flags: u8) -> bool {
18 if flags == 0 || flags & !ALL_CHUNK_FLAGS != 0 {
19 return false;
20 }
21 if flags & ChunkFlags::ERROR != 0 {
22 return flags == ChunkFlags::ERROR;
23 }
24 matches!(
25 flags,
26 ChunkFlags::FIRST | ChunkFlags::CONTINUE | ChunkFlags::LAST
27 ) || flags == (ChunkFlags::FIRST | ChunkFlags::LAST)
28}
29
30#[repr(C, packed)]
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct StreamChunk {
34 pub sequence: u32,
36 pub flags: u8,
38 pub len: u16,
40 }
42
43impl StreamChunk {
44 pub const SIZE: usize = 7;
46
47 pub fn new(sequence: u32, flags: u8, len: u16) -> Self {
49 Self {
50 sequence,
51 flags,
52 len,
53 }
54 }
55
56 pub fn first(sequence: u32, len: u16) -> Self {
58 Self::new(sequence, ChunkFlags::FIRST, len)
59 }
60
61 pub fn continuation(sequence: u32, len: u16) -> Self {
63 Self::new(sequence, ChunkFlags::CONTINUE, len)
64 }
65
66 pub fn last(sequence: u32, len: u16) -> Self {
68 Self::new(sequence, ChunkFlags::LAST, len)
69 }
70
71 pub fn error(sequence: u32, len: u16) -> Self {
73 Self::new(sequence, ChunkFlags::ERROR, len)
74 }
75
76 #[inline(always)]
78 pub fn from_bytes(bytes: &[u8]) -> Result<&Self, DCPError> {
79 if bytes.len() < Self::SIZE {
80 return Err(DCPError::InsufficientData);
81 }
82 if !valid_chunk_flags(bytes[4]) {
83 return Err(DCPError::ValidationFailed);
84 }
85 Ok(unsafe { &*(bytes.as_ptr() as *const Self) })
87 }
88
89 #[inline(always)]
91 pub fn as_bytes(&self) -> &[u8] {
92 unsafe { std::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
94 }
95
96 #[inline(always)]
98 pub fn is_first(&self) -> bool {
99 self.flags & ChunkFlags::FIRST != 0
100 }
101
102 #[inline(always)]
104 pub fn is_continuation(&self) -> bool {
105 self.flags & ChunkFlags::CONTINUE != 0
106 }
107
108 #[inline(always)]
110 pub fn is_last(&self) -> bool {
111 self.flags & ChunkFlags::LAST != 0
112 }
113
114 #[inline(always)]
116 pub fn is_error(&self) -> bool {
117 self.flags & ChunkFlags::ERROR != 0
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_chunk_size() {
127 assert_eq!(std::mem::size_of::<StreamChunk>(), 7);
128 }
129
130 #[test]
131 fn test_chunk_round_trip() {
132 let chunk = StreamChunk::new(12345, ChunkFlags::FIRST | ChunkFlags::LAST, 1024);
133 let bytes = chunk.as_bytes();
134 let parsed = StreamChunk::from_bytes(bytes).unwrap();
135
136 let sequence = parsed.sequence;
138 let flags = parsed.flags;
139 let len = parsed.len;
140
141 assert_eq!(sequence, 12345);
142 assert_eq!(flags, ChunkFlags::FIRST | ChunkFlags::LAST);
143 assert_eq!(len, 1024);
144 }
145
146 #[test]
147 fn test_flag_helpers() {
148 let first = StreamChunk::first(0, 100);
149 assert!(first.is_first());
150 assert!(!first.is_last());
151
152 let last = StreamChunk::last(1, 50);
153 assert!(last.is_last());
154 assert!(!last.is_first());
155
156 let error = StreamChunk::error(2, 0);
157 assert!(error.is_error());
158 }
159
160 #[test]
161 fn test_insufficient_data() {
162 let bytes = [0u8; 4];
163 assert_eq!(
164 StreamChunk::from_bytes(&bytes),
165 Err(DCPError::InsufficientData)
166 );
167 }
168}