#![forbid(unsafe_code)]
#![allow(dead_code)]
#[macro_use]
extern crate log;
extern crate num_traits;
use bitvec::prelude::*;
pub use chrono;
use chrono::prelude::*;
use chrono::{DateTime, TimeZone};
use std::collections::HashMap;
pub mod ais;
mod error;
pub mod gnss;
mod util;
pub use error::ParseError;
use util::*;
#[derive(Clone, Debug, PartialEq)]
pub enum ParsedMessage {
Incomplete,
VesselDynamicData(ais::VesselDynamicData),
VesselStaticData(ais::VesselStaticData),
BaseStationReport(ais::BaseStationReport),
BinaryAddressedMessage(ais::BinaryAddressedMessage),
StandardSarAircraftPositionReport(ais::StandardSarAircraftPositionReport),
UtcDateInquiry(ais::UtcDateInquiry),
UtcDateResponse(ais::BaseStationReport),
AddressedSafetyRelatedMessage(ais::AddressedSafetyRelatedMessage),
SafetyRelatedAcknowledgement(ais::SafetyRelatedAcknowledgement),
SafetyRelatedBroadcastMessage(ais::SafetyRelatedBroadcastMessage),
Interrogation(ais::Interrogation),
AssignmentModeCommand(ais::AssignmentModeCommand),
DgnssBroadcastBinaryMessage(ais::DgnssBroadcastBinaryMessage),
AidToNavigationReport(ais::AidToNavigationReport),
Gga(gnss::GgaData),
Rmc(gnss::RmcData),
Gsa(gnss::GsaData),
Gsv(Vec<gnss::GsvData>),
Vtg(gnss::VtgData),
Gll(gnss::GllData),
Alm(gnss::AlmData),
Dtm(gnss::DtmData),
Mss(gnss::MssData),
Stn(gnss::StnData),
Vbw(gnss::VbwData),
Zda(gnss::ZdaData),
}
pub trait LatLon {
fn latitude(&self) -> Option<f64>;
fn longitude(&self) -> Option<f64>;
}
#[derive(Clone)]
pub struct NmeaParser {
saved_fragments: HashMap<String, String>,
saved_vsds: HashMap<u32, ais::VesselStaticData>,
}
impl Default for NmeaParser {
fn default() -> Self {
Self::new()
}
}
impl NmeaParser {
pub fn new() -> NmeaParser {
NmeaParser {
saved_fragments: HashMap::new(),
saved_vsds: HashMap::new(),
}
}
pub fn reset(&mut self) {
self.saved_fragments.clear();
self.saved_vsds.clear();
}
fn push_string(&mut self, key: String, value: String) {
self.saved_fragments.insert(key, value);
}
fn pull_string(&mut self, key: String) -> Option<String> {
self.saved_fragments.remove(&key)
}
fn contains_key(&mut self, key: String) -> bool {
self.saved_fragments.contains_key(&key)
}
fn strings_count(&self) -> usize {
self.saved_fragments.len()
}
fn push_vsd(&mut self, mmsi: u32, vsd: ais::VesselStaticData) {
self.saved_vsds.insert(mmsi, vsd);
}
fn pull_vsd(&mut self, mmsi: u32) -> Option<ais::VesselStaticData> {
self.saved_vsds.remove(&mmsi)
}
fn vsds_count(&self) -> usize {
self.saved_vsds.len()
}
pub fn parse_sentence(&mut self, sentence: &str) -> Result<ParsedMessage, ParseError> {
let mut checksum = 0;
let (sentence, checksum_hex_given) = {
if let Some(pos) = sentence.rfind('*') {
if pos + 3 <= sentence.len() {
(
sentence[0..pos].to_string(),
sentence[(pos + 1)..(pos + 3)].to_string(),
)
} else {
debug!("Invalid checksum found for sentence: {}", sentence);
(sentence[0..pos].to_string(), "".to_string())
}
} else {
debug!("No checksum found for sentence: {}", sentence);
(sentence.to_string(), "".to_string())
}
};
for c in sentence.as_str().chars().skip(1) {
checksum ^= c as u8;
}
let checksum_hex_calculated = format!("{:02X?}", checksum);
if checksum_hex_calculated != checksum_hex_given && checksum_hex_given != "" {
return Err(ParseError::CorruptedSentence(format!(
"Corrupted NMEA sentence: {:02X?} != {:02X?}",
checksum_hex_calculated, checksum_hex_given
)));
}
let mut sentence_type: String = {
if let Some(i) = sentence.find(',') {
sentence[0..i].into()
} else {
return Err(ParseError::InvalidSentence(format!(
"Invalid NMEA sentence: {}",
sentence
)));
}
};
let nav_system = {
if &sentence_type[0..1] == "$" {
match &sentence_type[1..3] {
"GN" => Some(gnss::NavigationSystem::Combination),
"GP" => Some(gnss::NavigationSystem::Gps),
"GL" => Some(gnss::NavigationSystem::Glonass),
"GA" => Some(gnss::NavigationSystem::Galileo),
"BD" => Some(gnss::NavigationSystem::Beidou),
"GI" => Some(gnss::NavigationSystem::Navic),
"QZ" => Some(gnss::NavigationSystem::Qzss),
_ => Some(gnss::NavigationSystem::Other),
}
} else {
None
}
};
if nav_system != None {
if sentence_type.len() <= 6 {
sentence_type = format!("${}", &sentence_type[3..6]);
}
}
let station = {
if &sentence_type[0..1] == "!" {
match &sentence_type[1..3] {
"AB" => Some(ais::Station::BaseStation),
"AD" => Some(ais::Station::DependentAisBaseStation),
"AI" => Some(ais::Station::MobileStation),
"AN" => Some(ais::Station::AidToNavigationStation),
"AR" => Some(ais::Station::AisReceivingStation),
"AS" => Some(ais::Station::LimitedBaseStation),
"AT" => Some(ais::Station::AisTransmittingStation),
"AX" => Some(ais::Station::RepeaterStation),
_ => Some(ais::Station::Other),
}
} else {
None
}
};
if station != None {
if sentence_type.len() <= 6 {
sentence_type = format!("!{}", &sentence_type[3..6]);
}
}
match sentence_type.as_str() {
"$GGA" => gnss::gga::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$RMC" => gnss::rmc::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$GSA" => gnss::gsa::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$GSV" => gnss::gsv::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
self,
),
"$VTG" => gnss::vtg::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$GLL" => gnss::gll::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$ALM" => gnss::alm::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$DTM" => gnss::dtm::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$MSS" => gnss::mss::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$STN" => gnss::stn::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$VBW" => gnss::vbw::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"$ZDA" => gnss::zda::handle(
sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
),
"!VDM" | "!VDO" => {
let own_vessel = sentence_type.as_str() == "!VDO";
let mut fragment_count = 0;
let mut fragment_number = 0;
let mut message_id = None;
let mut radio_channel_code = None;
let mut payload_string: String = "".into();
for (num, s) in sentence.split(',').enumerate() {
match num {
1 => {
match s.parse::<u8>() {
Ok(i) => {
fragment_count = i;
}
Err(_) => {
return Err(ParseError::InvalidSentence(format!(
"Failed to parse fragment count: {}",
s
)));
}
};
}
2 => {
match s.parse::<u8>() {
Ok(i) => {
fragment_number = i;
}
Err(_) => {
return Err(ParseError::InvalidSentence(format!(
"Failed to parse fragment count: {}",
s
)));
}
};
}
3 => {
message_id = s.parse::<u64>().ok();
}
4 => {
radio_channel_code = Some(s);
}
5 => {
payload_string = s.to_string();
}
6 => {
}
_ => {}
}
}
let mut bv: Option<BitVec> = None;
if fragment_count == 1 {
bv = parse_payload(&payload_string).ok();
} else if fragment_count == 2 {
if let Some(msg_id) = message_id {
let key1 = make_fragment_key(
&sentence_type.to_string(),
msg_id,
fragment_count,
1,
radio_channel_code.unwrap_or(""),
);
let key2 = make_fragment_key(
&sentence_type.to_string(),
msg_id,
fragment_count,
2,
radio_channel_code.unwrap_or(""),
);
if fragment_number == 1 {
if let Some(p) = self.pull_string(key2) {
let mut payload_string_combined = payload_string;
payload_string_combined.push_str(p.as_str());
bv = parse_payload(&payload_string_combined).ok();
} else {
self.push_string(key1, payload_string);
}
} else if fragment_number == 2 {
if let Some(p) = self.pull_string(key1) {
let mut payload_string_combined = p;
payload_string_combined.push_str(payload_string.as_str());
bv = parse_payload(&payload_string_combined).ok();
} else {
self.push_string(key2, payload_string);
}
} else {
warn!(
"Unexpected NMEA fragment number: {}/{}",
fragment_number, fragment_count
);
}
} else {
warn!(
"NMEA message_id missing from {} than supported 2",
sentence_type
);
}
} else {
warn!(
"NMEA sentence fragment count greater ({}) than supported 2",
fragment_count
);
}
if let Some(bv) = bv {
let message_type = pick_u64(&bv, 0, 6);
match message_type {
1 | 2 | 3 => ais::vdm_t1t2t3::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
4 => ais::vdm_t4::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
5 => ais::vdm_t5::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
6 => ais::vdm_t6::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
7 => {
Err(ParseError::UnsupportedSentenceType(format!(
"Unsupported {} message type: {}",
sentence_type, message_type
)))
}
8 => {
Err(ParseError::UnsupportedSentenceType(format!(
"Unsupported {} message type: {}",
sentence_type, message_type
)))
}
9 => ais::vdm_t9::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
10 => ais::vdm_t10::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
11 => ais::vdm_t11::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
12 => ais::vdm_t12::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
13 => ais::vdm_t13::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
14 => ais::vdm_t14::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
15 => ais::vdm_t15::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
16 => ais::vdm_t16::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
17 => ais::vdm_t17::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
18 => ais::vdm_t18::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
19 => ais::vdm_t19::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
20 => {
Err(ParseError::UnsupportedSentenceType(format!(
"Unsupported {} message type: {}",
sentence_type, message_type
)))
}
21 => ais::vdm_t21::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
22 => {
Err(ParseError::UnsupportedSentenceType(format!(
"Unsupported {} message type: {}",
sentence_type, message_type
)))
}
23 => {
Err(ParseError::UnsupportedSentenceType(format!(
"Unsupported {} message type: {}",
sentence_type, message_type
)))
}
24 => ais::vdm_t24::handle(
&bv,
station.unwrap_or(ais::Station::Other),
self,
own_vessel,
),
25 => {
Err(ParseError::UnsupportedSentenceType(format!(
"Unsupported {} message type: {}",
sentence_type, message_type
)))
}
26 => {
Err(ParseError::UnsupportedSentenceType(format!(
"Unsupported {} message type: {}",
sentence_type, message_type
)))
}
27 => ais::vdm_t27::handle(
&bv,
station.unwrap_or(ais::Station::Other),
own_vessel,
),
_ => Err(ParseError::UnsupportedSentenceType(format!(
"Unsupported {} message type: {}",
sentence_type, message_type
))),
}
} else {
Ok(ParsedMessage::Incomplete)
}
}
_ => Err(ParseError::UnsupportedSentenceType(format!(
"Unsupported sentence type: {}",
sentence_type
))),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_corrupted() {
let mut p = NmeaParser::new();
assert!(p
.parse_sentence("!AIVDM,1,1,,A,38Id705000rRVJhE7cl9n;160000,0*41")
.ok()
.is_none());
}
#[test]
fn test_parse_missing_checksum() {
let mut p = NmeaParser::new();
assert!(p
.parse_sentence("!AIVDM,1,1,,A,38Id705000rRVJhE7cl9n;160000,0")
.ok()
.is_some());
}
#[test]
fn test_parse_invalid_utc() {
let mut p = NmeaParser::new();
assert_eq!(
p.parse_sentence("!AIVDM,1,1,,B,4028iqT47wP00wGiNbH8H0700`2H,0*13"),
Err(ParseError::InvalidSentence(String::from(
"Failed to parse Utc Date from y:4161 m:15 d:31 h:0 m:0 s:0"
)))
);
}
#[test]
fn test_nmea_parser() {
let mut p = NmeaParser::new();
p.push_string("a".into(), "b".into());
assert_eq!(p.strings_count(), 1);
p.push_string("c".into(), "d".into());
assert_eq!(p.strings_count(), 2);
p.pull_string("a".into());
assert_eq!(p.strings_count(), 1);
p.pull_string("c".into());
assert_eq!(p.strings_count(), 0);
p.push_vsd(1, Default::default());
assert_eq!(p.vsds_count(), 1);
p.push_vsd(2, Default::default());
assert_eq!(p.vsds_count(), 2);
p.pull_vsd(1);
assert_eq!(p.vsds_count(), 1);
p.pull_vsd(2);
assert_eq!(p.vsds_count(), 0);
}
#[test]
fn test_country() {
assert_eq!(vsd(230992580).country().unwrap(), "FI");
assert_eq!(vsd(276009860).country().unwrap(), "EE");
assert_eq!(vsd(265803690).country().unwrap(), "SE");
assert_eq!(vsd(273353180).country().unwrap(), "RU");
assert_eq!(vsd(211805060).country().unwrap(), "DE");
assert_eq!(vsd(257037270).country().unwrap(), "NO");
assert_eq!(vsd(227232370).country().unwrap(), "FR");
assert_eq!(vsd(248221000).country().unwrap(), "MT");
assert_eq!(vsd(374190000).country().unwrap(), "PA");
assert_eq!(vsd(412511368).country().unwrap(), "CN");
assert_eq!(vsd(512003200).country().unwrap(), "NZ");
assert_eq!(vsd(995126020).country(), None);
assert_eq!(vsd(2300049).country(), None);
assert_eq!(vsd(0).country(), None);
}
fn vsd(mmsi: u32) -> ais::VesselStaticData {
let mut vsd = ais::VesselStaticData::default();
vsd.mmsi = mmsi;
vsd
}
}