1use crate::message::{Field, ParsedSentence, MAX_FIELDS};
4use crate::types::{MessageType, NmeaMessage, ParseError, TalkerId};
5
6pub struct NmeaParser {}
8
9impl NmeaParser {
10 pub fn new() -> Self {
12 NmeaParser {}
13 }
14
15 pub fn parse_bytes(
24 &self,
25 data: &[u8],
26 ) -> Result<(Option<NmeaMessage>, usize), (ParseError, usize)> {
27 let start_pos = data.iter().position(|&b| b == b'$');
29
30 if start_pos.is_none() {
31 return Ok((None, data.len()));
33 }
34
35 let start_pos = start_pos.unwrap();
36
37 let end_pos = data[start_pos..]
39 .iter()
40 .position(|&b| b == b'\n' || b == b'\r');
41
42 if end_pos.is_none() {
43 return Ok((None, start_pos));
45 }
46
47 let end_pos = start_pos + end_pos.unwrap();
48 let sentence = &data[start_pos..end_pos];
49
50 match self.parse_sentence(sentence) {
52 Some(msg) => {
53 let mut consumed = end_pos + 1;
56 while consumed < data.len() && (data[consumed] == b'\r' || data[consumed] == b'\n')
57 {
58 consumed += 1;
59 }
60 Ok((Some(msg), consumed))
61 }
62 None => {
63 let mut consumed = end_pos + 1;
66 while consumed < data.len() && (data[consumed] == b'\r' || data[consumed] == b'\n')
67 {
68 consumed += 1;
69 }
70 Err((ParseError::InvalidMessage, consumed))
71 }
72 }
73 }
74
75 fn parse_sentence(&self, buffer: &[u8]) -> Option<NmeaMessage> {
77 if buffer.len() < 7 || buffer[0] != b'$' {
78 return None;
79 }
80
81 let sentence_end = buffer
83 .iter()
84 .position(|&b| b == b'*')
85 .unwrap_or(buffer.len());
86
87 if sentence_end < 7 {
88 return None;
89 }
90
91 let (talker_id, message_type) = self.identify_message(&buffer[1..6]);
93 if message_type == MessageType::Unknown {
94 return None;
95 }
96
97 let mut fields = [None; MAX_FIELDS];
99 let mut field_count = 0;
100 let mut field_start = 1; for i in 1..sentence_end {
103 if buffer[i] == b',' || i == sentence_end - 1 {
104 let field_end = if buffer[i] == b',' { i } else { i + 1 };
105
106 if field_count < MAX_FIELDS {
107 let field_bytes = &buffer[field_start..field_end];
108 if !field_bytes.is_empty() {
109 fields[field_count] = Some(Field::from_bytes(field_bytes));
110 }
111 field_count += 1;
112 }
113 field_start = i + 1;
114 }
115 }
116
117 let parsed = ParsedSentence {
118 message_type,
119 talker_id,
120 fields,
121 field_count,
122 };
123
124 match message_type {
126 MessageType::GGA => parsed.as_gga().map(NmeaMessage::GGA),
127 MessageType::RMC => parsed.as_rmc().map(NmeaMessage::RMC),
128 MessageType::GSA => parsed.as_gsa().map(NmeaMessage::GSA),
129 MessageType::GSV => parsed.as_gsv().map(NmeaMessage::GSV),
130 MessageType::GLL => parsed.as_gll().map(NmeaMessage::GLL),
131 MessageType::VTG => parsed.as_vtg().map(NmeaMessage::VTG),
132 MessageType::GNS => parsed.as_gns().map(NmeaMessage::GNS),
133 MessageType::Unknown => None,
134 }
135 }
136
137 fn identify_message(&self, header_bytes: &[u8]) -> (TalkerId, MessageType) {
139 if header_bytes.len() < 5 {
140 return (TalkerId::Unknown, MessageType::Unknown);
141 }
142
143 let talker_id = match &header_bytes[0..2] {
144 b"GP" => TalkerId::GP,
145 b"GL" => TalkerId::GL,
146 b"GA" => TalkerId::GA,
147 b"GB" => TalkerId::GB,
148 b"GN" => TalkerId::GN,
149 b"BD" => TalkerId::BD,
150 b"QZ" => TalkerId::QZ,
151 _ => TalkerId::Unknown,
152 };
153
154 let message_type = match &header_bytes[2..5] {
155 b"GGA" => MessageType::GGA,
156 b"RMC" => MessageType::RMC,
157 b"GSA" => MessageType::GSA,
158 b"GSV" => MessageType::GSV,
159 b"GLL" => MessageType::GLL,
160 b"VTG" => MessageType::VTG,
161 b"GNS" => MessageType::GNS,
162 _ => MessageType::Unknown,
163 };
164
165 (talker_id, message_type)
166 }
167}
168
169impl Default for NmeaParser {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175#[cfg(test)]
176impl NmeaParser {
177 pub(crate) fn parse_sentence_complete(&self, sentence: &[u8]) -> Option<NmeaMessage> {
180 match self.parse_bytes(sentence) {
181 Ok((msg, _consumed)) => msg,
182 Err(_) => None,
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
193 fn test_valid_gga_from_reference() {
194 let parser = NmeaParser::new();
195 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
196
197 let result = parser.parse_bytes(sentence);
198 assert!(result.is_ok());
199 let (msg, consumed) = result.unwrap();
200 assert!(msg.is_some());
201 assert_eq!(consumed, sentence.len());
202
203 let msg = msg.unwrap();
204 assert_eq!(msg.message_type(), MessageType::GGA);
205
206 let gga = msg.as_gga().expect("Should parse as GGA");
207 assert_eq!(gga.time(), "123519");
208 assert_eq!(gga.latitude, 4807.038);
209 assert_eq!(gga.lat_direction, 'N');
210 assert_eq!(gga.longitude, 1131.000);
211 assert_eq!(gga.lon_direction, 'E');
212 }
213
214 #[test]
215 fn test_valid_rmc_from_reference() {
216 let parser = NmeaParser::new();
217 let sentence = b"$GPRMC,235947,A,5540.123,N,01231.456,E,000.0,360.0,130694,011.3,E*62\r\n";
218
219 let result = parser.parse_bytes(sentence);
220 assert!(result.is_ok());
221 let (msg, consumed) = result.unwrap();
222 assert!(msg.is_some());
223 assert_eq!(consumed, sentence.len());
224
225 let msg = msg.unwrap();
226 assert_eq!(msg.message_type(), MessageType::RMC);
227
228 let rmc = msg.as_rmc().expect("Should parse as RMC");
229 assert_eq!(rmc.time(), "235947");
230 assert_eq!(rmc.status, 'A');
231 assert_eq!(rmc.latitude, 5540.123);
232 assert_eq!(rmc.lat_direction, 'N');
233 }
234
235 #[test]
236 fn test_valid_gsa_from_reference() {
237 let parser = NmeaParser::new();
238 let sentence = b"$GPGSA,A,3,04,05,09,12,24,25,29,31,,,,,1.8,1.0,1.5*33\r\n";
239
240 let result = parser.parse_bytes(sentence);
241 assert!(result.is_ok());
242 let (msg, consumed) = result.unwrap();
243 assert!(msg.is_some());
244 assert_eq!(consumed, sentence.len());
245
246 let msg = msg.unwrap();
247 assert_eq!(msg.message_type(), MessageType::GSA);
248
249 let gsa = msg.as_gsa().expect("Should parse as GSA");
250 assert_eq!(gsa.mode, 'A');
251 assert_eq!(gsa.fix_type, 3);
252 }
253
254 #[test]
255 fn test_valid_gsv_from_reference() {
256 let parser = NmeaParser::new();
257 let sentence = b"$GPGSV,3,1,12,02,17,315,44,04,77,268,47,05,55,147,45,07,32,195,42*70\r\n";
258
259 let result = parser.parse_bytes(sentence);
260 assert!(result.is_ok());
261 let (msg, consumed) = result.unwrap();
262 assert!(msg.is_some());
263 assert_eq!(consumed, sentence.len());
264
265 let msg = msg.unwrap();
266 assert_eq!(msg.message_type(), MessageType::GSV);
267
268 let gsv = msg.as_gsv().expect("Should parse as GSV");
269 assert_eq!(gsv.num_messages, 3);
270 assert_eq!(gsv.message_num, 1);
271 assert_eq!(gsv.satellites_in_view, 12);
272 }
273
274 #[test]
276 fn test_edge_case_gga_empty_fields() {
277 let parser = NmeaParser::new();
278 let sentence = b"$GPGGA,123519,,,,,,0,00,99.99,,,,,,*48\r\n";
280
281 let result = parser.parse_bytes(sentence);
282 assert!(result.is_err());
283 let (err, _consumed) = result.unwrap_err();
284 assert_eq!(err, ParseError::InvalidMessage);
285 }
286
287 #[test]
288 fn test_edge_case_rmc_zero_coordinates() {
289 let parser = NmeaParser::new();
290 let sentence = b"$GPRMC,000000,A,0000.000,N,00000.000,E,000.0,000.0,000000,000.0,W*7C\r\n";
292
293 let result = parser.parse_bytes(sentence);
294 assert!(result.is_ok());
295 let (msg, consumed) = result.unwrap();
296 assert!(msg.is_some());
297 assert_eq!(consumed, sentence.len());
298
299 let msg = msg.unwrap();
300 assert_eq!(msg.message_type(), MessageType::RMC);
301
302 let rmc = msg.as_rmc().expect("Should parse as RMC");
303 assert_eq!(rmc.latitude, 0.0);
304 assert_eq!(rmc.longitude, 0.0);
305 }
306
307 #[test]
308 fn test_edge_case_ais_message_with_exclamation() {
309 let parser = NmeaParser::new();
310 let sentence = b"!AIVDM,1,1,,B,15N:;R0P00PD;88MD5MTDwwP0<0L,0*5C\r\n";
312
313 let result = parser.parse_bytes(sentence);
314 assert!(result.is_ok());
316 let (msg, consumed) = result.unwrap();
317 assert!(msg.is_none());
318 assert_eq!(consumed, sentence.len());
319 }
320
321 #[test]
322 fn test_edge_case_concatenated_messages() {
323 let parser = NmeaParser::new();
324 let data = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47$GPRMC,235947,A,5540.123,N,01231.456,E,000.0,360.0,130694,011.3,E*62\r\n";
327
328 let result = parser.parse_bytes(data);
329 assert!(result.is_ok());
332 let (msg, consumed) = result.unwrap();
333 assert!(msg.is_some());
334 let msg = msg.unwrap();
335 assert_eq!(msg.message_type(), MessageType::GGA);
336 assert_eq!(consumed, data.len());
337 }
338
339 #[test]
340 fn test_edge_case_unsupported_message_types() {
341 let parser = NmeaParser::new();
342
343 let txt_sentence = b"$GPTXT,01,01,02,Software Version 7.03.00 (12345)*6E\r\n";
345 let result = parser.parse_bytes(txt_sentence);
346 assert!(result.is_err());
347 let (err, _consumed) = result.unwrap_err();
348 assert_eq!(err, ParseError::InvalidMessage);
349
350 let xte_sentence = b"$GPXTE,A,A,0.67,L,N*6F\r\n";
352 let result2 = parser.parse_bytes(xte_sentence);
353 assert!(result2.is_err());
354 let (err2, _consumed2) = result2.unwrap_err();
355 assert_eq!(err2, ParseError::InvalidMessage);
356 }
357
358 #[test]
360 fn test_invalid_wrong_checksum() {
361 let parser = NmeaParser::new();
362 let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*00\r\n";
364
365 let result = parser.parse_bytes(sentence);
367 assert!(result.is_ok());
368 let (msg, consumed) = result.unwrap();
369 assert!(msg.is_some());
370 assert_eq!(consumed, sentence.len());
371 }
372
373 #[test]
374 fn test_invalid_rmc_void_status() {
375 let parser = NmeaParser::new();
376 let sentence = b"$GPRMC,235947,V,5540.123,N,01231.456,E,000.0,360.0,130694,011.3,E*00\r\n";
378
379 let result = parser.parse_bytes(sentence);
380 assert!(result.is_ok());
381 let (msg, consumed) = result.unwrap();
382 assert!(msg.is_some());
383 assert_eq!(consumed, sentence.len());
384
385 let msg = msg.unwrap();
386 let rmc = msg.as_rmc().expect("Should parse as RMC");
387 assert_eq!(rmc.status, 'V'); }
389
390 #[test]
391 fn test_invalid_missing_checksum() {
392 let parser = NmeaParser::new();
393 let sentence = b"$GPGSA,A,3,04,05,09,12,24,25,29,31,,,,,1.8,1.0,1.5\r\n";
395
396 let result = parser.parse_bytes(sentence);
397 assert!(result.is_ok());
399 let (msg, consumed) = result.unwrap();
400 assert!(msg.is_some());
401 assert_eq!(consumed, sentence.len());
402 }
403
404 #[test]
405 fn test_invalid_missing_dollar_sign() {
406 let parser = NmeaParser::new();
407 let sentence = b"GPGSV,3,1,12,02,17,315,44,04,77,268,47,05,55,147,45,07,32,195,42*70\r\n";
409
410 let result = parser.parse_bytes(sentence);
411 assert!(result.is_ok());
413 let (msg, consumed) = result.unwrap();
414 assert!(msg.is_none());
415 assert_eq!(consumed, sentence.len());
416 }
417
418 #[test]
419 fn test_multiple_valid_messages_in_sequence() {
420 let parser = NmeaParser::new();
421
422 let gga = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
424 let result1 = parser.parse_bytes(gga);
425 assert!(result1.is_ok());
426 let (msg1, consumed1) = result1.unwrap();
427 assert!(msg1.is_some());
428 assert_eq!(consumed1, gga.len());
429
430 let rmc = b"$GPRMC,235947,A,5540.123,N,01231.456,E,000.0,360.0,130694,011.3,E*62\r\n";
432 let result2 = parser.parse_bytes(rmc);
433 assert!(result2.is_ok());
434 let (msg2, consumed2) = result2.unwrap();
435 assert!(msg2.is_some());
436 assert_eq!(consumed2, rmc.len());
437
438 let gsa = b"$GPGSA,A,3,04,05,09,12,24,25,29,31,,,,,1.8,1.0,1.5*33\r\n";
440 let result3 = parser.parse_bytes(gsa);
441 assert!(result3.is_ok());
442 let (msg3, consumed3) = result3.unwrap();
443 assert!(msg3.is_some());
444 assert_eq!(consumed3, gsa.len());
445 }
446}