1use crate::error::WireError;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26#[repr(u8)]
27#[allow(missing_docs)]
28pub enum SubmessageId {
29 Pad = 0x01,
30 AckNack = 0x06,
31 Heartbeat = 0x07,
32 Gap = 0x08,
33 InfoTs = 0x09,
34 InfoSrc = 0x0A,
35 InfoReplyIp4 = 0x0C,
36 InfoDst = 0x0E,
37 InfoReply = 0x0F,
38 NackFrag = 0x12,
39 HeartbeatFrag = 0x13,
40 Data = 0x15,
41 DataFrag = 0x16,
42}
43
44impl SubmessageId {
45 #[must_use]
47 pub fn as_u8(self) -> u8 {
48 self as u8
49 }
50
51 pub fn from_u8(byte: u8) -> Result<Self, WireError> {
59 match byte {
60 0x01 => Ok(Self::Pad),
61 0x06 => Ok(Self::AckNack),
62 0x07 => Ok(Self::Heartbeat),
63 0x08 => Ok(Self::Gap),
64 0x09 => Ok(Self::InfoTs),
65 0x0A => Ok(Self::InfoSrc),
66 0x0C => Ok(Self::InfoReplyIp4),
67 0x0E => Ok(Self::InfoDst),
68 0x0F => Ok(Self::InfoReply),
69 0x12 => Ok(Self::NackFrag),
70 0x13 => Ok(Self::HeartbeatFrag),
71 0x15 => Ok(Self::Data),
72 0x16 => Ok(Self::DataFrag),
73 other => Err(WireError::UnknownSubmessageId { id: other }),
74 }
75 }
76}
77
78pub const FLAG_E_LITTLE_ENDIAN: u8 = 0x01;
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
83pub struct SubmessageHeader {
84 pub submessage_id: SubmessageId,
86 pub flags: u8,
89 pub octets_to_next_header: u16,
92}
93
94impl SubmessageHeader {
95 pub const WIRE_SIZE: usize = 4;
97
98 #[must_use]
100 pub fn is_little_endian(self) -> bool {
101 (self.flags & FLAG_E_LITTLE_ENDIAN) != 0
102 }
103
104 #[must_use]
109 pub fn to_bytes(self) -> [u8; 4] {
110 let mut out = [0u8; 4];
111 out[0] = self.submessage_id.as_u8();
112 out[1] = self.flags;
113 let len_bytes = if self.is_little_endian() {
114 self.octets_to_next_header.to_le_bytes()
115 } else {
116 self.octets_to_next_header.to_be_bytes()
117 };
118 out[2..].copy_from_slice(&len_bytes);
119 out
120 }
121
122 pub fn from_bytes(bytes: &[u8]) -> Result<Self, WireError> {
127 if bytes.len() < Self::WIRE_SIZE {
128 return Err(WireError::UnexpectedEof {
129 needed: Self::WIRE_SIZE,
130 offset: 0,
131 });
132 }
133 let id = SubmessageId::from_u8(bytes[0])?;
134 let flags = bytes[1];
135 let mut len_bytes = [0u8; 2];
136 len_bytes.copy_from_slice(&bytes[2..4]);
137 let octets_to_next_header = if (flags & FLAG_E_LITTLE_ENDIAN) != 0 {
138 u16::from_le_bytes(len_bytes)
139 } else {
140 u16::from_be_bytes(len_bytes)
141 };
142 Ok(Self {
143 submessage_id: id,
144 flags,
145 octets_to_next_header,
146 })
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 #![allow(clippy::expect_used, clippy::unwrap_used)]
153 use super::*;
154
155 #[test]
156 fn submessage_id_data_is_0x15() {
157 assert_eq!(SubmessageId::Data.as_u8(), 0x15);
158 }
159
160 #[test]
161 fn submessage_id_heartbeat_is_0x07() {
162 assert_eq!(SubmessageId::Heartbeat.as_u8(), 0x07);
163 }
164
165 #[test]
166 fn submessage_id_acknack_is_0x06() {
167 assert_eq!(SubmessageId::AckNack.as_u8(), 0x06);
168 }
169
170 #[test]
171 fn submessage_id_gap_is_0x08() {
172 assert_eq!(SubmessageId::Gap.as_u8(), 0x08);
173 }
174
175 #[test]
176 fn submessage_id_roundtrip_for_known_ids() {
177 for id in [
178 SubmessageId::Pad,
179 SubmessageId::AckNack,
180 SubmessageId::Heartbeat,
181 SubmessageId::Gap,
182 SubmessageId::InfoTs,
183 SubmessageId::InfoSrc,
184 SubmessageId::InfoReplyIp4,
185 SubmessageId::InfoDst,
186 SubmessageId::InfoReply,
187 SubmessageId::NackFrag,
188 SubmessageId::HeartbeatFrag,
189 SubmessageId::Data,
190 SubmessageId::DataFrag,
191 ] {
192 assert_eq!(SubmessageId::from_u8(id.as_u8()).unwrap(), id);
193 }
194 }
195
196 #[test]
197 fn submessage_id_rejects_unknown_byte() {
198 let res = SubmessageId::from_u8(0xFE);
199 assert!(matches!(
200 res,
201 Err(WireError::UnknownSubmessageId { id: 0xFE })
202 ));
203 }
204
205 #[test]
206 fn submessage_header_layout_le() {
207 let h = SubmessageHeader {
208 submessage_id: SubmessageId::Data,
209 flags: FLAG_E_LITTLE_ENDIAN,
210 octets_to_next_header: 0x0102,
211 };
212 let bytes = h.to_bytes();
213 assert_eq!(bytes[0], 0x15); assert_eq!(bytes[1], 0x01); assert_eq!(bytes[2], 0x02);
217 assert_eq!(bytes[3], 0x01);
218 }
219
220 #[test]
221 fn submessage_header_layout_be() {
222 let h = SubmessageHeader {
223 submessage_id: SubmessageId::Heartbeat,
224 flags: 0, octets_to_next_header: 0x0102,
226 };
227 let bytes = h.to_bytes();
228 assert_eq!(bytes[0], 0x07);
229 assert_eq!(bytes[1], 0);
230 assert_eq!(bytes[2], 0x01);
232 assert_eq!(bytes[3], 0x02);
233 }
234
235 #[test]
236 fn submessage_header_is_little_endian_flag() {
237 let le = SubmessageHeader {
238 submessage_id: SubmessageId::Data,
239 flags: FLAG_E_LITTLE_ENDIAN,
240 octets_to_next_header: 0,
241 };
242 assert!(le.is_little_endian());
243 let be = SubmessageHeader {
244 submessage_id: SubmessageId::Data,
245 flags: 0,
246 octets_to_next_header: 0,
247 };
248 assert!(!be.is_little_endian());
249 }
250
251 #[test]
252 fn submessage_header_roundtrip_le() {
253 let h = SubmessageHeader {
254 submessage_id: SubmessageId::Data,
255 flags: FLAG_E_LITTLE_ENDIAN,
256 octets_to_next_header: 0xABCD,
257 };
258 let bytes = h.to_bytes();
259 assert_eq!(SubmessageHeader::from_bytes(&bytes).unwrap(), h);
260 }
261
262 #[test]
263 fn submessage_header_roundtrip_be() {
264 let h = SubmessageHeader {
265 submessage_id: SubmessageId::Heartbeat,
266 flags: 0,
267 octets_to_next_header: 0x1234,
268 };
269 let bytes = h.to_bytes();
270 assert_eq!(SubmessageHeader::from_bytes(&bytes).unwrap(), h);
271 }
272
273 #[test]
274 fn submessage_header_decode_rejects_truncated() {
275 let bytes = [0x15u8, 0x01]; let res = SubmessageHeader::from_bytes(&bytes);
277 assert!(matches!(
278 res,
279 Err(WireError::UnexpectedEof { needed: 4, .. })
280 ));
281 }
282
283 #[test]
284 fn submessage_header_decode_rejects_unknown_id() {
285 let bytes = [0xFEu8, 0x01, 0, 0];
286 let res = SubmessageHeader::from_bytes(&bytes);
287 assert!(matches!(
288 res,
289 Err(WireError::UnknownSubmessageId { id: 0xFE })
290 ));
291 }
292}