rustedbytes_nmea/
lib.rs

1#![no_std]
2
3//! NMEA 0183 parser library
4//!
5//! This library provides a `no_std` compatible NMEA 0183 parser for parsing
6//! GPS/GNSS data from receivers.
7
8mod message;
9mod parser;
10mod types;
11
12// Re-export public API
13pub use message::{Field, GgaData, GllData, GsaData, GsvData, RmcData, SatelliteInfo, VtgData};
14pub use parser::NmeaParser;
15pub use types::*;
16
17/// Parse result type: returns optional message and bytes consumed, or error with bytes consumed
18pub type ParseResult = Result<(Option<NmeaMessage>, usize), (ParseError, usize)>;
19
20#[cfg(test)]
21mod tests {
22    use super::*;
23
24    /// Helper function to test message parsing
25    fn test_message_parsing(sentence: &[u8], expected_type: MessageType) {
26        let parser = NmeaParser::new();
27        let result = parser.parse_bytes(sentence);
28        assert!(result.is_ok());
29        let (msg, consumed) = result.unwrap();
30        assert_eq!(consumed, sentence.len());
31        let msg = msg.unwrap();
32        assert_eq!(msg.message_type(), expected_type);
33    }
34
35    #[test]
36    fn test_parser_initialization() {
37        let _parser = NmeaParser::new();
38        // Parser is stateless, no internal state to check
39    }
40
41    #[test]
42    fn test_parse_all_message_types() {
43        let test_cases = [
44            (
45                b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n".as_slice(),
46                MessageType::GGA,
47            ),
48            (
49                b"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n"
50                    .as_slice(),
51                MessageType::RMC,
52            ),
53            (
54                b"$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n".as_slice(),
55                MessageType::GSA,
56            ),
57            (
58                b"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n"
59                    .as_slice(),
60                MessageType::GSV,
61            ),
62            (
63                b"$GPGLL,4916.45,N,12311.12,W,225444,A,*1D\r\n".as_slice(),
64                MessageType::GLL,
65            ),
66            (
67                b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n".as_slice(),
68                MessageType::VTG,
69            ),
70        ];
71
72        for (sentence, expected_type) in test_cases {
73            test_message_parsing(sentence, expected_type);
74        }
75    }
76
77    #[test]
78    fn test_multiple_messages_stream() {
79        let parser = NmeaParser::new();
80        let stream = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n\
81                       $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n\
82                       $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n";
83
84        let mut message_count = 0;
85        let mut offset = 0;
86        while offset < stream.len() {
87            let result = parser.parse_bytes(&stream[offset..]);
88            if result.is_err() {
89                break;
90            }
91            let (msg, consumed) = result.unwrap();
92            if consumed == 0 {
93                break;
94            }
95            offset += consumed;
96            if msg.is_some() {
97                message_count += 1;
98            }
99        }
100
101        assert_eq!(message_count, 3);
102    }
103
104    #[test]
105    fn test_field_extraction() {
106        let parser = NmeaParser::new();
107        let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
108
109        let result = parser.parse_bytes(sentence);
110        assert!(result.is_ok());
111        let (msg, consumed) = result.unwrap();
112        assert_eq!(consumed, sentence.len());
113        let msg = msg.unwrap();
114
115        assert_eq!(msg.message_type(), MessageType::GGA);
116        let gga = msg.as_gga().expect("Expected GGA message");
117        assert_eq!(gga.time(), "123519");
118        assert_eq!(gga.latitude, 4807.038);
119    }
120
121    #[test]
122    fn test_invalid_and_partial_sentences() {
123        let parser = NmeaParser::new();
124
125        // Test invalid data without $
126        let invalid = b"INVALID DATA\r\n";
127        let result = parser.parse_bytes(invalid);
128        assert!(result.is_ok());
129        let (msg, consumed) = result.unwrap();
130        assert!(msg.is_none());
131        assert_eq!(consumed, invalid.len());
132
133        // Test partial message
134        let partial = b"$GPGGA,123519,4807.038,N";
135        let result = parser.parse_bytes(partial);
136        assert!(result.is_ok());
137        let (msg, consumed) = result.unwrap();
138        assert!(msg.is_none());
139        assert_eq!(consumed, 0);
140    }
141
142    #[test]
143    fn test_gga_parameters() {
144        let parser = NmeaParser::new();
145        let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
146
147        let result = parser.parse_bytes(sentence);
148        assert!(result.is_ok());
149        let (msg, _) = result.unwrap();
150        let msg = msg.unwrap();
151        let gga = msg.as_gga();
152        assert!(gga.is_some());
153
154        let gga_data = gga.unwrap();
155        assert_eq!(gga_data.time(), "123519");
156        assert_eq!(gga_data.latitude, 4807.038);
157        assert_eq!(gga_data.lat_direction, 'N');
158        assert_eq!(gga_data.longitude, 1131.000);
159        assert_eq!(gga_data.lon_direction, 'E');
160        assert_eq!(gga_data.fix_quality, 1);
161        assert_eq!(gga_data.num_satellites, Some(8));
162        assert_eq!(gga_data.hdop, Some(0.9));
163        assert_eq!(gga_data.altitude, Some(545.4));
164        assert_eq!(gga_data.altitude_units, Some('M'));
165    }
166
167    #[test]
168    fn test_rmc_parameters() {
169        let parser = NmeaParser::new();
170        let sentence = b"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
171
172        let result = parser.parse_bytes(sentence);
173        assert!(result.is_ok());
174        let (msg, _) = result.unwrap();
175        let msg = msg.unwrap();
176        let rmc = msg.as_rmc();
177        assert!(rmc.is_some());
178
179        let rmc_data = rmc.unwrap();
180        assert_eq!(rmc_data.time(), "123519");
181        assert_eq!(rmc_data.status, 'A');
182        assert_eq!(rmc_data.latitude, 4807.038);
183        assert_eq!(rmc_data.lat_direction, 'N');
184        assert_eq!(rmc_data.longitude, 1131.000);
185        assert_eq!(rmc_data.lon_direction, 'E');
186        assert_eq!(rmc_data.speed_knots, 22.4);
187        assert_eq!(rmc_data.track_angle, 84.4);
188        assert_eq!(rmc_data.date(), "230394");
189    }
190
191    #[test]
192    fn test_gsa_parameters() {
193        let parser = NmeaParser::new();
194        let sentence = b"$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n";
195
196        let result = parser.parse_bytes(sentence);
197        assert!(result.is_ok());
198        let (msg, _) = result.unwrap();
199        let msg = msg.unwrap();
200        let gsa = msg.as_gsa();
201        assert!(gsa.is_some());
202
203        let gsa_data = gsa.unwrap();
204        assert_eq!(gsa_data.mode, 'A');
205        assert_eq!(gsa_data.fix_type, 3);
206        assert_eq!(gsa_data.satellite_ids[0], Some(4));
207        assert_eq!(gsa_data.satellite_ids[1], Some(5));
208        assert_eq!(gsa_data.satellite_ids[3], Some(9));
209        assert_eq!(gsa_data.pdop, Some(2.5));
210        assert_eq!(gsa_data.hdop, Some(1.3));
211        assert_eq!(gsa_data.vdop, Some(2.1));
212    }
213
214    #[test]
215    fn test_gsv_parameters() {
216        let parser = NmeaParser::new();
217        let sentence = b"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
218
219        let result = parser.parse_bytes(sentence);
220        assert!(result.is_ok());
221        let (msg, _) = result.unwrap();
222        let msg = msg.unwrap();
223        let gsv = msg.as_gsv();
224        assert!(gsv.is_some());
225
226        let gsv_data = gsv.unwrap();
227        assert_eq!(gsv_data.num_messages, 2);
228        assert_eq!(gsv_data.message_num, 1);
229        assert_eq!(gsv_data.satellites_in_view, 8);
230
231        // Check first satellite
232        assert!(gsv_data.satellite_info[0].is_some());
233        let sat1 = gsv_data.satellite_info[0].as_ref().unwrap();
234        assert_eq!(sat1.prn, Some(1));
235        assert_eq!(sat1.elevation, Some(40));
236        assert_eq!(sat1.azimuth, Some(83));
237        assert_eq!(sat1.snr, Some(46));
238    }
239
240    #[test]
241    fn test_gll_parameters() {
242        let parser = NmeaParser::new();
243        let sentence = b"$GPGLL,4916.45,N,12311.12,W,225444,A,*1D\r\n";
244
245        let result = parser.parse_bytes(sentence);
246        assert!(result.is_ok());
247        let (msg, _) = result.unwrap();
248        let msg = msg.unwrap();
249        let gll = msg.as_gll();
250        assert!(gll.is_some());
251
252        let gll_data = gll.unwrap();
253        assert_eq!(gll_data.latitude, 4916.45);
254        assert_eq!(gll_data.lat_direction, 'N');
255        assert_eq!(gll_data.longitude, 12311.12);
256        assert_eq!(gll_data.lon_direction, 'W');
257        assert_eq!(gll_data.time(), "225444");
258        assert_eq!(gll_data.status, 'A');
259    }
260
261    #[test]
262    fn test_vtg_parameters() {
263        let parser = NmeaParser::new();
264        let sentence = b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n";
265
266        let result = parser.parse_bytes(sentence);
267        assert!(result.is_ok());
268        let (msg, _) = result.unwrap();
269        let msg = msg.unwrap();
270        let vtg = msg.as_vtg();
271        assert!(vtg.is_some());
272
273        let vtg_data = vtg.unwrap();
274        assert_eq!(vtg_data.track_true, Some(54.7));
275        assert_eq!(vtg_data.track_true_indicator, Some('T'));
276        assert_eq!(vtg_data.track_magnetic, Some(34.4));
277        assert_eq!(vtg_data.track_magnetic_indicator, Some('M'));
278        assert_eq!(vtg_data.speed_knots, Some(5.5));
279        assert_eq!(vtg_data.speed_knots_indicator, Some('N'));
280        assert_eq!(vtg_data.speed_kph, Some(10.2));
281        assert_eq!(vtg_data.speed_kph_indicator, Some('K'));
282    }
283
284    #[test]
285    fn test_wrong_message_type_extraction() {
286        let parser = NmeaParser::new();
287        let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
288
289        let result = parser.parse_bytes(sentence);
290        assert!(result.is_ok());
291        let (msg, _) = result.unwrap();
292        let msg = msg.unwrap();
293
294        // Try to extract RMC data from a GGA message
295        let rmc = msg.as_rmc();
296        assert!(rmc.is_none());
297
298        // GGA extraction should work
299        let gga = msg.as_gga();
300        assert!(gga.is_some());
301    }
302
303    #[test]
304    fn test_gga_with_empty_fields() {
305        let parser = NmeaParser::new();
306        // GGA message with some empty mandatory fields should fail to parse
307        let sentence = b"$GPGGA,123519,,N,,E,1,,,,,M,,M,,*47\r\n";
308
309        let result = parser.parse_bytes(sentence);
310        // Should return error because mandatory fields (latitude, longitude) are empty
311        assert!(result.is_err());
312        let (err, _consumed) = result.unwrap_err();
313        assert_eq!(err, ParseError::InvalidMessage);
314    }
315
316    #[test]
317    fn test_rmc_with_empty_status() {
318        let parser = NmeaParser::new();
319        // RMC message with void status (still valid)
320        let sentence = b"$GPRMC,123519,V,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
321
322        let result = parser.parse_bytes(sentence);
323        assert!(result.is_ok());
324        let (msg, _) = result.unwrap();
325        let msg = msg.unwrap();
326        let rmc = msg.as_rmc();
327        assert!(rmc.is_some());
328
329        let rmc_data = rmc.unwrap();
330        assert_eq!(rmc_data.status, 'V');
331    }
332
333    #[test]
334    fn test_gsa_with_partial_satellites() {
335        let parser = NmeaParser::new();
336        // GSA message with only a few satellites
337        let sentence = b"$GPGSA,A,3,01,,,,,,,,,,,,2.5,1.3,2.1*39\r\n";
338
339        let result = parser.parse_bytes(sentence);
340        assert!(result.is_ok());
341        let (msg, _) = result.unwrap();
342        let msg = msg.unwrap();
343        let gsa = msg.as_gsa();
344        assert!(gsa.is_some());
345
346        let gsa_data = gsa.unwrap();
347        assert_eq!(gsa_data.mode, 'A');
348        assert_eq!(gsa_data.fix_type, 3);
349        assert_eq!(gsa_data.satellite_ids[0], Some(1));
350        assert_eq!(gsa_data.satellite_ids[1], None);
351        assert_eq!(gsa_data.pdop, Some(2.5));
352        assert_eq!(gsa_data.hdop, Some(1.3));
353        assert_eq!(gsa_data.vdop, Some(2.1));
354    }
355
356    #[test]
357    fn test_gsv_with_partial_satellite_data() {
358        let parser = NmeaParser::new();
359        // GSV message with only two satellites
360        let sentence = b"$GPGSV,1,1,02,01,40,083,46,02,17,308,*75\r\n";
361
362        let result = parser.parse_bytes(sentence);
363        assert!(result.is_ok());
364        let (msg, _) = result.unwrap();
365        let msg = msg.unwrap();
366        let gsv = msg.as_gsv();
367        assert!(gsv.is_some());
368
369        let gsv_data = gsv.unwrap();
370        assert_eq!(gsv_data.num_messages, 1);
371        assert_eq!(gsv_data.satellites_in_view, 2);
372
373        // First satellite should be complete
374        assert!(gsv_data.satellite_info[0].is_some());
375        let sat1 = gsv_data.satellite_info[0].as_ref().unwrap();
376        assert_eq!(sat1.prn, Some(1));
377        assert_eq!(sat1.elevation, Some(40));
378        assert_eq!(sat1.azimuth, Some(83));
379        assert_eq!(sat1.snr, Some(46));
380
381        // Second satellite should have missing SNR
382        assert!(gsv_data.satellite_info[1].is_some());
383        let sat2 = gsv_data.satellite_info[1].as_ref().unwrap();
384        assert_eq!(sat2.prn, Some(2));
385        assert_eq!(sat2.elevation, Some(17));
386        assert_eq!(sat2.azimuth, Some(308));
387        assert_eq!(sat2.snr, None);
388
389        // Third and fourth should be None
390        assert!(gsv_data.satellite_info[2].is_none());
391        assert!(gsv_data.satellite_info[3].is_none());
392    }
393
394    #[test]
395    fn test_numeric_type_parsing() {
396        let parser = NmeaParser::new();
397        let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
398
399        let result = parser.parse_bytes(sentence);
400        assert!(result.is_ok());
401        let (msg, _) = result.unwrap();
402        let msg = msg.unwrap();
403        let gga = msg.as_gga();
404        assert!(gga.is_some());
405
406        let gga_data = gga.unwrap();
407
408        // Verify types are correctly parsed
409        assert!((gga_data.latitude - 4807.038).abs() < 0.001);
410        assert!((gga_data.longitude - 1131.000).abs() < 0.001);
411
412        if let Some(hdop) = gga_data.hdop {
413            assert!((hdop - 0.9).abs() < 0.01);
414        }
415
416        if let Some(alt) = gga_data.altitude {
417            assert!((alt - 545.4).abs() < 0.1);
418        }
419    }
420
421    #[test]
422    fn test_messages_with_missing_mandatory_fields() {
423        let parser = NmeaParser::new();
424        let test_cases = [
425            b"$GPGGA,,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n".as_slice(), // Missing time
426            b"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,,003.1,W*6A\r\n".as_slice(), // Missing date
427            b"$GPGSA,,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n".as_slice(), // Missing mode
428            b"$GPGSV,,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n".as_slice(), // Missing num_messages
429            b"$GPGLL,4916.45,N,12311.12,W,225444,,*1D\r\n".as_slice(), // Missing status
430        ];
431
432        for sentence in test_cases {
433            let result = parser.parse_bytes(sentence);
434            assert!(result.is_err());
435            let (err, _consumed) = result.unwrap_err();
436            assert_eq!(err, ParseError::InvalidMessage);
437        }
438    }
439
440    // New tests for the updated parsing logic
441    #[test]
442    fn test_parse_with_spurious_characters() {
443        let parser = NmeaParser::new();
444        // Data with spurious characters before the message
445        let data = b"GARBAGE$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
446
447        let result = parser.parse_bytes(data);
448        assert!(result.is_ok());
449        let (msg, consumed) = result.unwrap();
450        assert!(msg.is_some());
451        assert_eq!(consumed, data.len());
452    }
453
454    #[test]
455    fn test_parse_multiple_messages_in_buffer() {
456        let parser = NmeaParser::new();
457        let data = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n\
458                     $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
459
460        // Parse first message
461        let result1 = parser.parse_bytes(data);
462        assert!(result1.is_ok());
463        let (msg1, consumed1) = result1.unwrap();
464        assert!(msg1.is_some());
465        assert_eq!(msg1.unwrap().message_type(), MessageType::GGA);
466
467        // Parse second message
468        let result2 = parser.parse_bytes(&data[consumed1..]);
469        assert!(result2.is_ok());
470        let (msg2, consumed2) = result2.unwrap();
471        assert!(msg2.is_some());
472        assert_eq!(msg2.unwrap().message_type(), MessageType::RMC);
473
474        // Total consumed should be the entire buffer
475        assert_eq!(consumed1 + consumed2, data.len());
476    }
477
478    #[test]
479    fn test_partial_message_returns_none() {
480        let parser = NmeaParser::new();
481        // Partial message without line ending
482        let partial = b"$GPGGA,123519,4807.038,N,01131.000,E,1";
483
484        let result = parser.parse_bytes(partial);
485        assert!(result.is_ok());
486        let (msg, consumed) = result.unwrap();
487        assert!(msg.is_none());
488        assert_eq!(consumed, 0); // No bytes consumed for partial message
489    }
490
491    #[test]
492    fn test_spurious_only_data() {
493        let parser = NmeaParser::new();
494        // Only spurious data without any message
495        let spurious = b"GARBAGE DATA WITHOUT DOLLAR SIGN\r\n";
496
497        let result = parser.parse_bytes(spurious);
498        assert!(result.is_ok());
499        let (msg, consumed) = result.unwrap();
500        assert!(msg.is_none());
501        assert_eq!(consumed, spurious.len()); // All spurious data consumed
502    }
503
504    #[test]
505    fn test_bytes_consumed_tracking() {
506        let parser = NmeaParser::new();
507        let data = b"JUNK$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\nMORE";
508
509        let result = parser.parse_bytes(data);
510        assert!(result.is_ok());
511        let (msg, consumed) = result.unwrap();
512        assert!(msg.is_some());
513        // Should consume up to and including the \n, but not "MORE"
514        assert!(consumed < data.len());
515        assert!(consumed > 0);
516        assert_eq!(&data[consumed..], b"MORE");
517    }
518
519    #[test]
520    fn test_invalid_message_returns_error() {
521        let parser = NmeaParser::new();
522        // Complete message but with missing mandatory field
523        let invalid = b"$GPGGA,,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
524
525        let result = parser.parse_bytes(invalid);
526        assert!(result.is_err());
527        let (err, _consumed) = result.unwrap_err();
528        assert_eq!(err, ParseError::InvalidMessage);
529    }
530
531    #[test]
532    fn test_multiple_messages_with_different_timestamps() {
533        let parser = NmeaParser::new();
534        let data = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n\
535                     $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
536
537        // Parse first message
538        let result1 = parser.parse_bytes(data);
539        assert!(result1.is_ok());
540        let (msg1, consumed1) = result1.unwrap();
541        assert!(msg1.is_some());
542        let msg1 = msg1.unwrap();
543        assert_eq!(msg1.message_type(), MessageType::GGA);
544
545        // Parse second message
546        let result2 = parser.parse_bytes(&data[consumed1..]);
547        assert!(result2.is_ok());
548        let (msg2, _consumed2) = result2.unwrap();
549        assert!(msg2.is_some());
550        let msg2 = msg2.unwrap();
551        assert_eq!(msg2.message_type(), MessageType::RMC);
552    }
553}