1#![forbid(unsafe_code)]
26#![allow(dead_code)]
27#![cfg_attr(not(test), no_std)]
28
29#[macro_use]
30extern crate log;
31
32extern crate num_traits;
33
34#[macro_use]
35extern crate alloc;
36
37use alloc::string::{String, ToString};
38use alloc::vec::Vec;
39use bitvec::prelude::*;
40pub use chrono;
41use chrono::prelude::*;
42use chrono::{DateTime, TimeZone};
43use hashbrown::HashMap;
44use core::cmp::max;
45use core::str::FromStr;
46
47#[cfg(not(test))]
48use num_traits::float::FloatCore;
49
50pub mod ais;
51mod error;
52pub mod gnss;
53mod util;
54mod json_date_time_utc;
55mod json_fixed_offset;
56
57pub use error::ParseError;
58use util::*;
59
60#[derive(Clone, Debug, PartialEq)]
65pub enum ParsedMessage {
66 Incomplete,
69
70 VesselDynamicData(ais::VesselDynamicData),
72
73 VesselStaticData(ais::VesselStaticData),
75
76 BaseStationReport(ais::BaseStationReport),
78
79 BinaryAddressedMessage(ais::BinaryAddressedMessage),
81 StandardSarAircraftPositionReport(ais::StandardSarAircraftPositionReport),
90
91 UtcDateInquiry(ais::UtcDateInquiry),
93
94 UtcDateResponse(ais::BaseStationReport),
96
97 AddressedSafetyRelatedMessage(ais::AddressedSafetyRelatedMessage),
99
100 SafetyRelatedAcknowledgement(ais::SafetyRelatedAcknowledgement),
102
103 SafetyRelatedBroadcastMessage(ais::SafetyRelatedBroadcastMessage),
105
106 Interrogation(ais::Interrogation),
108
109 AssignmentModeCommand(ais::AssignmentModeCommand),
111
112 DgnssBroadcastBinaryMessage(ais::DgnssBroadcastBinaryMessage),
114
115 DataLinkManagementMessage(ais::DataLinkManagementMessage),
117
118 AidToNavigationReport(ais::AidToNavigationReport),
120
121 ChannelManagement(ais::ChannelManagement),
123
124 GroupAssignmentCommand(ais::GroupAssignmentCommand),
126
127 SingleSlotBinaryMessage(ais::SingleSlotBinaryMessage),
129
130 MultipleSlotBinaryMessage(ais::MultipleSlotBinaryMessage),
132
133 Gga(gnss::GgaData),
135
136 Rmc(gnss::RmcData),
138
139 Gns(gnss::GnsData),
141
142 Gsa(gnss::GsaData),
144
145 Gsv(Vec<gnss::GsvData>),
147
148 Vtg(gnss::VtgData),
150
151 Gll(gnss::GllData),
153
154 Alm(gnss::AlmData),
156
157 Dtm(gnss::DtmData),
159
160 Mss(gnss::MssData),
162
163 Stn(gnss::StnData),
165
166 Vbw(gnss::VbwData),
168
169 Zda(gnss::ZdaData),
171
172 Dpt(gnss::DptData),
174
175 Dbs(gnss::DbsData),
177
178 Mtw(gnss::MtwData),
180
181 Vhw(gnss::VhwData),
183
184 Hdt(gnss::HdtData),
186
187 Mwv(gnss::MwvData),
189}
190
191pub trait LatLon {
195 fn latitude(&self) -> Option<f64>;
198
199 fn longitude(&self) -> Option<f64>;
202}
203
204#[derive(Clone)]
210pub struct NmeaParser {
211 saved_fragments: HashMap<String, String>,
212 saved_vsds: HashMap<u32, ais::VesselStaticData>,
213}
214
215impl Default for NmeaParser {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221impl NmeaParser {
222 pub fn new() -> NmeaParser {
224 NmeaParser {
225 saved_fragments: HashMap::new(),
226 saved_vsds: HashMap::new(),
227 }
228 }
229
230 pub fn reset(&mut self) {
233 self.saved_fragments.clear();
234 self.saved_vsds.clear();
235 }
236
237 fn push_string(&mut self, key: String, value: String) {
239 self.saved_fragments.insert(key, value);
240 }
241
242 fn pull_string(&mut self, key: String) -> Option<String> {
244 self.saved_fragments.remove(&key)
245 }
246
247 fn contains_key(&mut self, key: String) -> bool {
249 self.saved_fragments.contains_key(&key)
250 }
251
252 fn strings_count(&self) -> usize {
254 self.saved_fragments.len()
255 }
256
257 fn push_vsd(&mut self, mmsi: u32, vsd: ais::VesselStaticData) {
259 self.saved_vsds.insert(mmsi, vsd);
260 }
261
262 fn pull_vsd(&mut self, mmsi: u32) -> Option<ais::VesselStaticData> {
264 self.saved_vsds.remove(&mmsi)
265 }
266
267 fn vsds_count(&self) -> usize {
269 self.saved_vsds.len()
270 }
271
272 pub fn parse_sentence(&mut self, sentence: &str) -> Result<ParsedMessage, ParseError> {
277 let sentence = {
279 if let Some(start_idx) = sentence.find(['$', '!']) {
280 &sentence[start_idx..]
281 } else {
282 return Err(ParseError::InvalidSentence(format!(
283 "Invalid NMEA sentence: {}",
284 sentence
285 )));
286 }
287 };
288
289 let mut checksum = 0;
292 let (sentence, checksum_hex_given) = {
293 if let Some(pos) = sentence.rfind('*') {
294 if pos + 3 <= sentence.len() {
295 (
296 sentence[0..pos].to_string(),
297 sentence[(pos + 1)..(pos + 3)].to_string(),
298 )
299 } else {
300 debug!("Invalid checksum found for sentence: {}", sentence);
301 (sentence[0..pos].to_string(), "".to_string())
302 }
303 } else {
304 debug!("No checksum found for sentence: {}", sentence);
305 (sentence.to_string(), "".to_string())
306 }
307 };
308 for c in sentence.as_str().chars().skip(1) {
309 checksum ^= c as u8;
310 }
311 let checksum_hex_calculated = format!("{:02X?}", checksum);
312 if checksum_hex_calculated != checksum_hex_given && !checksum_hex_given.is_empty() {
313 return Err(ParseError::CorruptedSentence(format!(
314 "Corrupted NMEA sentence: {:02X?} != {:02X?}",
315 checksum_hex_calculated, checksum_hex_given
316 )));
317 }
318
319 let sentence_type = {
321 if let Some(i) = sentence.find(',') {
322 &sentence[0..i]
323 } else {
324 return Err(ParseError::InvalidSentence(format!(
325 "Invalid NMEA sentence: {}",
326 sentence
327 )));
328 }
329 };
330
331 if !sentence_type
333 .chars()
334 .all(|c| c.is_ascii_alphanumeric() || c == '$' || c == '!')
335 {
336 return Err(ParseError::InvalidSentence(format!(
337 "Invalid characters in sentence type: {}",
338 sentence_type
339 )));
340 }
341
342 let (nav_system, station, sentence_type) = if sentence_type.starts_with('$') {
343 let nav_system = gnss::NavigationSystem::from_str(
345 sentence_type
346 .get(1..)
347 .ok_or(ParseError::CorruptedSentence("Empty String".to_string()))?,
348 )?;
349 let sentence_type = if !sentence_type.starts_with('P') && sentence_type.len() == 6 {
350 format!(
351 "${}",
352 sentence_type
353 .get(3..6)
354 .ok_or(ParseError::InvalidSentence(format!(
355 "{sentence_type} is too short."
356 )))?
357 )
358 } else {
359 String::from(sentence_type)
360 };
361 (nav_system, ais::Station::Other, sentence_type)
362 } else if sentence_type.starts_with('!') {
363 let station = ais::Station::from_str(
365 sentence_type
366 .get(1..)
367 .ok_or(ParseError::CorruptedSentence("Empty String".to_string()))?,
368 )?;
369 let sentence_type = if sentence_type.len() == 6 {
370 format!(
371 "!{}",
372 sentence_type
373 .get(3..6)
374 .ok_or(ParseError::InvalidSentence(format!(
375 "{sentence_type} is too short."
376 )))?
377 )
378 } else {
379 String::from(sentence_type)
380 };
381 (gnss::NavigationSystem::Other, station, sentence_type)
382 } else {
383 (
384 gnss::NavigationSystem::Other,
385 ais::Station::Other,
386 String::from(sentence_type),
387 )
388 };
389
390 match sentence_type.as_str() {
392 "$GGA" => gnss::gga::handle(sentence.as_str(), nav_system),
394 "$RMC" => gnss::rmc::handle(sentence.as_str(), nav_system),
396 "$GNS" => gnss::gns::handle(sentence.as_str(), nav_system),
398 "$GSA" => gnss::gsa::handle(sentence.as_str(), nav_system),
400 "$GSV" => gnss::gsv::handle(sentence.as_str(), nav_system, self),
402 "$VTG" => gnss::vtg::handle(sentence.as_str(), nav_system),
404 "$GLL" => gnss::gll::handle(sentence.as_str(), nav_system),
406 "$ALM" => gnss::alm::handle(sentence.as_str(), nav_system),
408 "$DTM" => gnss::dtm::handle(sentence.as_str(), nav_system),
410 "$MSS" => gnss::mss::handle(sentence.as_str(), nav_system),
412 "$STN" => gnss::stn::handle(sentence.as_str(), nav_system),
414 "$VBW" => gnss::vbw::handle(sentence.as_str(), nav_system),
416 "$ZDA" => gnss::zda::handle(sentence.as_str(), nav_system),
418
419 "!VDM" | "!VDO" => {
421 let own_vessel = sentence_type.as_str() == "!VDO";
422 let mut fragment_count = 0;
423 let mut fragment_number = 0;
424 let mut message_id = None;
425 let mut radio_channel_code = None;
426 let mut payload_string: String = "".into();
427 for (num, s) in sentence.split(',').enumerate() {
428 match num {
429 1 => {
430 match s.parse::<u8>() {
431 Ok(i) => {
432 fragment_count = i;
433 }
434 Err(_) => {
435 return Err(ParseError::InvalidSentence(format!(
436 "Failed to parse fragment count: {}",
437 s
438 )));
439 }
440 };
441 }
442 2 => {
443 match s.parse::<u8>() {
444 Ok(i) => {
445 fragment_number = i;
446 }
447 Err(_) => {
448 return Err(ParseError::InvalidSentence(format!(
449 "Failed to parse fragment count: {}",
450 s
451 )));
452 }
453 };
454 }
455 3 => {
456 message_id = s.parse::<u64>().ok();
457 }
458 4 => {
459 radio_channel_code = Some(s);
461 }
462 5 => {
463 payload_string = s.to_string();
464 }
465 6 => {
466 }
468 _ => {}
469 }
470 }
471
472 let mut bv: Option<BitVec> = None;
474 match fragment_count {
475 1 => bv = parse_payload(&payload_string).ok(),
476 2 => {
477 if let Some(msg_id) = message_id {
478 let key1 = make_fragment_key(
479 &sentence_type.to_string(),
480 msg_id,
481 fragment_count,
482 1,
483 radio_channel_code.unwrap_or(""),
484 );
485 let key2 = make_fragment_key(
486 &sentence_type.to_string(),
487 msg_id,
488 fragment_count,
489 2,
490 radio_channel_code.unwrap_or(""),
491 );
492 match fragment_number {
493 1 => {
494 if let Some(p) = self.pull_string(key2) {
495 let mut payload_string_combined = payload_string;
496 payload_string_combined.push_str(p.as_str());
497 bv = parse_payload(&payload_string_combined).ok();
498 } else {
499 self.push_string(key1, payload_string);
500 }
501 }
502 2 => {
503 if let Some(p) = self.pull_string(key1) {
504 let mut payload_string_combined = p;
505 payload_string_combined.push_str(payload_string.as_str());
506 bv = parse_payload(&payload_string_combined).ok();
507 } else {
508 self.push_string(key2, payload_string);
509 }
510 }
511 _ => {
512 warn!(
513 "Unexpected NMEA fragment number: {}/{}",
514 fragment_number, fragment_count
515 );
516 }
517 }
518 } else {
519 warn!(
520 "NMEA message_id missing from {} than supported 2",
521 sentence_type
522 );
523 }
524 }
525 _ => {
526 warn!(
527 "NMEA sentence fragment count greater ({}) than supported 2",
528 fragment_count
529 );
530 }
531 }
532
533 if let Some(bv) = bv {
534 let message_type = pick_u64(&bv, 0, 6);
535 match message_type {
536 1..=3 => ais::vdm_t1t2t3::handle(&bv, station, own_vessel),
538 4 => ais::vdm_t4::handle(&bv, station, own_vessel),
540 5 => ais::vdm_t5::handle(&bv, station, own_vessel),
542 6 => ais::vdm_t6::handle(&bv, station, own_vessel),
544 7 => {
546 Err(ParseError::UnsupportedSentenceType(format!(
548 "Unsupported {} message type: {}",
549 sentence_type, message_type
550 )))
551 }
552 8 => {
554 Err(ParseError::UnsupportedSentenceType(format!(
556 "Unsupported {} message type: {}",
557 sentence_type, message_type
558 )))
559 }
560 9 => ais::vdm_t9::handle(&bv, station, own_vessel),
562 10 => ais::vdm_t10::handle(&bv, station, own_vessel),
564 11 => ais::vdm_t11::handle(&bv, station, own_vessel),
566 12 => ais::vdm_t12::handle(&bv, station, own_vessel),
568 13 => ais::vdm_t13::handle(&bv, station, own_vessel),
570 14 => ais::vdm_t14::handle(&bv, station, own_vessel),
572 15 => ais::vdm_t15::handle(&bv, station, own_vessel),
574 16 => ais::vdm_t16::handle(&bv, station, own_vessel),
576 17 => ais::vdm_t17::handle(&bv, station, own_vessel),
578 18 => ais::vdm_t18::handle(&bv, station, own_vessel),
580 19 => ais::vdm_t19::handle(&bv, station, own_vessel),
582 20 => ais::vdm_t20::handle(&bv, station, own_vessel),
584 21 => ais::vdm_t21::handle(&bv, station, own_vessel),
586 22 => ais::vdm_t22::handle(&bv, station, own_vessel),
588 23 => ais::vdm_t23::handle(&bv, station, own_vessel),
590 24 => ais::vdm_t24::handle(&bv, station, self, own_vessel),
592 25 => ais::vdm_t25::handle(&bv, station, own_vessel),
594 26 => ais::vdm_t26::handle(&bv, station, own_vessel),
596 27 => ais::vdm_t27::handle(&bv, station, own_vessel),
598 _ => Err(ParseError::UnsupportedSentenceType(format!(
599 "Unsupported {} message type: {}",
600 sentence_type, message_type
601 ))),
602 }
603 } else {
604 Ok(ParsedMessage::Incomplete)
605 }
606 }
607 "$DPT" => gnss::dpt::handle(sentence.as_str()),
608 "$DBS" => gnss::dbs::handle(sentence.as_str()),
609 "$MTW" => gnss::mtw::handle(sentence.as_str()),
610 "$VHW" => gnss::vhw::handle(sentence.as_str()),
611 "$HDT" => gnss::hdt::handle(sentence.as_str()),
612 "$MWV" => gnss::mwv::handle(sentence.as_str()),
613 _ => Err(ParseError::UnsupportedSentenceType(format!(
614 "Unsupported sentence type: {}",
615 sentence_type
616 ))),
617 }
618 }
619}
620
621#[cfg(test)]
622mod test {
623 use super::*;
624 #[test]
625 fn test_parse_invalid_sentence() {
626 let mut p = NmeaParser::new();
627 assert_eq!(
628 p.parse_sentence("$GAGSV,,"),
629 Err(ParseError::InvalidSentence(
630 "Invalid characters in sentence type: $\u{7b4}GAGSV".to_string()
631 ))
632 );
633 assert_eq!(
634 p.parse_sentence("$WIMWV,295.4,T,"),
635 Err(ParseError::CorruptedSentence(
636 "pick string for \"wind_speed_knots\" was None".to_string()
637 ))
638 );
639 assert_eq!(
640 p.parse_sentence("!AIVDM,not,a,valid,nmea,string,0*00"),
641 Err(ParseError::CorruptedSentence(
642 "Corrupted NMEA sentence: \"17\" != \"00\"".to_string()
643 ))
644 );
645 assert_eq!(
646 p.parse_sentence("!"),
647 Err(ParseError::InvalidSentence(
648 "Invalid NMEA sentence: !".to_string()
649 ))
650 );
651 }
652 #[test]
653 fn test_parse_prefix_chars() {
654 let mut p = NmeaParser::new();
656 assert!(p
657 .parse_sentence(",1277,-106*35\r\n!AIVDM,1,1,,A,152IS=iP?w<tSF0l4Q@>4?wp0H:;,0*2")
658 .ok()
659 .is_some());
660 }
661
662 #[test]
663 fn test_parse_corrupted() {
664 let mut p = NmeaParser::new();
666 assert!(p
667 .parse_sentence("!AIVDM,1,1,,A,38Id705000rRVJhE7cl9n;160000,0*41")
668 .ok()
669 .is_none());
670 }
671
672 #[test]
673 fn test_parse_missing_checksum() {
674 let mut p = NmeaParser::new();
676 assert!(p
677 .parse_sentence("!AIVDM,1,1,,A,38Id705000rRVJhE7cl9n;160000,0")
678 .ok()
679 .is_some());
680 }
681
682 #[test]
683 fn test_parse_invalid_utc() {
684 let mut p = NmeaParser::new();
686 assert_eq!(
687 p.parse_sentence("!AIVDM,1,1,,B,4028iqT47wP00wGiNbH8H0700`2H,0*13"),
688 Err(ParseError::InvalidSentence(String::from(
689 "Failed to parse Utc Date from y:4161 m:15 d:31 h:0 m:0 s:0"
690 )))
691 );
692 }
693
694 #[test]
695 fn test_parse_proprietary() {
696 }
714
715 #[test]
716 fn test_parse_invalid_talker() {
717 let mut p = NmeaParser::new();
719 assert_eq!(
720 p.parse_sentence("$QQ,*2C"),
721 Err(ParseError::UnsupportedSentenceType(String::from(
722 "Unsupported sentence type: $QQ"
723 )))
724 );
725 assert_eq!(
726 p.parse_sentence("$A,a0,*10"),
727 Err(ParseError::InvalidSentence(String::from(
728 "Invalid talker identifier"
729 )))
730 );
731 assert_eq!(
732 p.parse_sentence("$,0a,*51"),
733 Err(ParseError::InvalidSentence(String::from(
734 "Invalid talker identifier"
735 )))
736 );
737 }
738
739 #[test]
740 fn test_nmea_parser() {
741 let mut p = NmeaParser::new();
742
743 p.push_string("a".into(), "b".into());
745 assert_eq!(p.strings_count(), 1);
746 p.push_string("c".into(), "d".into());
747 assert_eq!(p.strings_count(), 2);
748 p.pull_string("a".into());
749 assert_eq!(p.strings_count(), 1);
750 p.pull_string("c".into());
751 assert_eq!(p.strings_count(), 0);
752
753 p.push_vsd(1, Default::default());
755 assert_eq!(p.vsds_count(), 1);
756 p.push_vsd(2, Default::default());
757 assert_eq!(p.vsds_count(), 2);
758 p.pull_vsd(1);
759 assert_eq!(p.vsds_count(), 1);
760 p.pull_vsd(2);
761 assert_eq!(p.vsds_count(), 0);
762 }
763
764 #[test]
765 fn test_country() {
766 assert_eq!(vsd(230992580).country().unwrap(), "FI");
767 assert_eq!(vsd(276009860).country().unwrap(), "EE");
768 assert_eq!(vsd(265803690).country().unwrap(), "SE");
769 assert_eq!(vsd(273353180).country().unwrap(), "RU");
770 assert_eq!(vsd(211805060).country().unwrap(), "DE");
771 assert_eq!(vsd(257037270).country().unwrap(), "NO");
772 assert_eq!(vsd(227232370).country().unwrap(), "FR");
773 assert_eq!(vsd(248221000).country().unwrap(), "MT");
774 assert_eq!(vsd(374190000).country().unwrap(), "PA");
775 assert_eq!(vsd(412511368).country().unwrap(), "CN");
776 assert_eq!(vsd(512003200).country().unwrap(), "NZ");
777 assert_eq!(vsd(995126020).country(), None);
778 assert_eq!(vsd(2300049).country(), None);
779 assert_eq!(vsd(0).country(), None);
780 }
781
782 fn vsd(mmsi: u32) -> ais::VesselStaticData {
784 let mut vsd = ais::VesselStaticData::default();
785 vsd.mmsi = mmsi;
786 vsd
787 }
788}