1use crate::crc::crc16_ccitt;
10use crate::error::{SbfError, SbfResult};
11
12pub const SBF_SYNC: [u8; 2] = [0x24, 0x40]; pub const MIN_BLOCK_LENGTH: u16 = 8;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct SbfHeader {
21 pub crc: u16,
23 pub block_id: u16,
25 pub block_rev: u8,
27 pub length: u16,
29 pub tow_ms: u32,
31 pub wnc: u16,
33}
34
35impl SbfHeader {
36 pub fn parse(data: &[u8]) -> SbfResult<Self> {
44 if data.len() < 6 {
45 return Err(SbfError::IncompleteBlock {
46 needed: 6,
47 have: data.len(),
48 });
49 }
50
51 let crc = u16::from_le_bytes([data[0], data[1]]);
52 let id_rev = u16::from_le_bytes([data[2], data[3]]);
53 let length = u16::from_le_bytes([data[4], data[5]]);
54
55 let block_id = id_rev & 0x1FFF;
56 let block_rev = ((id_rev >> 13) & 0x07) as u8;
57
58 if length < MIN_BLOCK_LENGTH || (length & 0x03) != 0 {
60 return Err(SbfError::InvalidLength(length));
61 }
62
63 let (tow_ms, wnc) = if data.len() >= 12 {
65 (
66 u32::from_le_bytes([data[6], data[7], data[8], data[9]]),
67 u16::from_le_bytes([data[10], data[11]]),
68 )
69 } else {
70 (0xFFFFFFFF, 0xFFFF)
71 };
72
73 Ok(Self {
74 crc,
75 block_id,
76 block_rev,
77 length,
78 tow_ms,
79 wnc,
80 })
81 }
82
83 pub fn parse_from_block(block_data: &[u8]) -> SbfResult<Self> {
91 if block_data.len() < 2 {
92 return Err(SbfError::IncompleteBlock {
93 needed: 2,
94 have: block_data.len(),
95 });
96 }
97
98 if block_data[0] != SBF_SYNC[0] || block_data[1] != SBF_SYNC[1] {
100 return Err(SbfError::InvalidSync);
101 }
102
103 Self::parse(&block_data[2..])
104 }
105
106 pub fn validate_crc(&self, block_data: &[u8]) -> SbfResult<()> {
114 let length = self.length as usize;
115 if block_data.len() < length {
116 return Err(SbfError::IncompleteBlock {
117 needed: length,
118 have: block_data.len(),
119 });
120 }
121
122 let calculated_crc = crc16_ccitt(&block_data[4..length]);
124
125 if calculated_crc != self.crc {
126 return Err(SbfError::CrcMismatch {
127 expected: self.crc,
128 actual: calculated_crc,
129 });
130 }
131
132 Ok(())
133 }
134
135 pub fn tow_seconds(&self) -> Option<f64> {
139 if self.tow_ms == 0xFFFFFFFF {
140 None
141 } else {
142 Some(self.tow_ms as f64 * 0.001)
143 }
144 }
145
146 pub fn tow_ms_raw(&self) -> Option<u32> {
150 if self.tow_ms == 0xFFFFFFFF {
151 None
152 } else {
153 Some(self.tow_ms)
154 }
155 }
156
157 pub fn week_number(&self) -> Option<u16> {
161 if self.wnc == 0xFFFF {
162 None
163 } else {
164 Some(self.wnc)
165 }
166 }
167
168 pub fn has_valid_time(&self) -> bool {
170 self.tow_ms != 0xFFFFFFFF && self.wnc != 0xFFFF
171 }
172
173 pub const fn body_offset() -> usize {
178 12 }
180}
181
182impl std::fmt::Display for SbfHeader {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 write!(
185 f,
186 "SbfHeader {{ id: {}, rev: {}, len: {}, tow: {}ms, wnc: {} }}",
187 self.block_id, self.block_rev, self.length, self.tow_ms, self.wnc
188 )
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_header_parse() {
198 let data = [
200 0x00, 0x00, 0xAB, 0x0F, 0x10, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x64, 0x00, ];
206
207 let header = SbfHeader::parse(&data).unwrap();
208 assert_eq!(header.block_id, 4011);
209 assert_eq!(header.block_rev, 0);
210 assert_eq!(header.length, 16);
211 assert_eq!(header.tow_ms, 1000);
212 assert_eq!(header.wnc, 100);
213 }
214
215 #[test]
216 fn test_header_id_rev_extraction() {
217 let data = [
220 0x00, 0x00, 0xA7, 0x4F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
226
227 let header = SbfHeader::parse(&data).unwrap();
228 assert_eq!(header.block_id, 4007);
229 assert_eq!(header.block_rev, 2);
230 }
231
232 #[test]
233 fn test_header_invalid_length() {
234 let data = [
236 0x00, 0x00, 0xAB, 0x0F, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
242
243 let result = SbfHeader::parse(&data);
244 assert!(matches!(result, Err(SbfError::InvalidLength(9))));
245 }
246
247 #[test]
248 fn test_header_too_short() {
249 let data = [0x00, 0x00, 0x00];
250
251 let result = SbfHeader::parse(&data);
252 assert!(matches!(result, Err(SbfError::IncompleteBlock { .. })));
253 }
254
255 #[test]
256 fn test_tow_seconds() {
257 let header = SbfHeader {
258 crc: 0,
259 block_id: 4007,
260 block_rev: 0,
261 length: 16,
262 tow_ms: 1500,
263 wnc: 100,
264 };
265
266 assert_eq!(header.tow_seconds(), Some(1.5));
267
268 let header_no_tow = SbfHeader {
269 tow_ms: 0xFFFFFFFF,
270 ..header
271 };
272 assert_eq!(header_no_tow.tow_seconds(), None);
273 }
274
275 #[test]
276 fn test_parse_from_block_with_sync() {
277 let block = [
278 0x24, 0x40, 0x00, 0x00, 0xAB, 0x0F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
285
286 let header = SbfHeader::parse_from_block(&block).unwrap();
287 assert_eq!(header.block_id, 4011);
288 }
289
290 #[test]
291 fn test_parse_from_block_invalid_sync() {
292 let block = [
293 0x00, 0x00, 0x00, 0x00, 0xAB, 0x0F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
300
301 let result = SbfHeader::parse_from_block(&block);
302 assert!(matches!(result, Err(SbfError::InvalidSync)));
303 }
304}