use crate::ais::armor::{extract_i32, extract_u32};
use super::utils::{decode_latitude, decode_longitude};
#[derive(Debug, Clone, PartialEq)]
pub struct BaseStationReport {
pub mmsi: u32,
pub year: Option<u16>,
pub month: Option<u8>,
pub day: Option<u8>,
pub hour: Option<u8>,
pub minute: Option<u8>,
pub second: Option<u8>,
pub position_accuracy: bool,
pub longitude: Option<f64>,
pub latitude: Option<f64>,
pub type_of_epfd: u8,
}
impl BaseStationReport {
pub(crate) fn decode(bits: &[u8]) -> Option<Self> {
if bits.len() < 168 {
return None;
}
let mmsi = extract_u32(bits, 8, 30)?;
let year_raw = extract_u32(bits, 38, 14)?;
let month_raw = extract_u32(bits, 52, 4)? as u8;
let day_raw = extract_u32(bits, 56, 5)? as u8;
let hour_raw = extract_u32(bits, 61, 5)? as u8;
let minute_raw = extract_u32(bits, 66, 6)? as u8;
let second_raw = extract_u32(bits, 72, 6)? as u8;
let accuracy = extract_u32(bits, 78, 1)? == 1;
let lon_raw = extract_i32(bits, 79, 28)?;
let lat_raw = extract_i32(bits, 107, 27)?;
let epfd = extract_u32(bits, 134, 4)? as u8;
Some(Self {
mmsi,
year: if year_raw == 0 {
None
} else {
Some(year_raw as u16)
},
month: if month_raw == 0 {
None
} else {
Some(month_raw)
},
day: if day_raw == 0 { None } else { Some(day_raw) },
hour: if hour_raw == 24 { None } else { Some(hour_raw) },
minute: if minute_raw == 60 {
None
} else {
Some(minute_raw)
},
second: if second_raw == 60 {
None
} else {
Some(second_raw)
},
position_accuracy: accuracy,
longitude: decode_longitude(lon_raw),
latitude: decode_latitude(lat_raw),
type_of_epfd: epfd,
})
}
}
#[cfg(test)]
mod tests {
use crate::ais::{AisMessage, AisParser};
use crate::parse_frame;
#[test]
fn base_station_gpsd() {
let mut parser = AisParser::new();
let frame = parse_frame("!AIVDM,1,1,,A,403OviQuMGCqWrRO9>E6fE700@GO,0*4D").expect("valid");
let msg = parser.decode(&frame).expect("decoded");
if let AisMessage::BaseStation(report) = msg {
assert!(report.mmsi > 0, "MMSI must be set");
if let (Some(lat), Some(lon)) = (report.latitude, report.longitude) {
assert!((-90.0..=90.0).contains(&lat), "lat out of range: {lat}");
assert!((-180.0..=180.0).contains(&lon), "lon out of range: {lon}");
}
} else {
panic!("expected BaseStation, got {msg:?}");
}
}
#[test]
fn base_station_utc_sentinel() {
let mut parser = AisParser::new();
let frame = parse_frame("!AIVDM,1,1,,A,403OviQuMGCqWrRO9>E6fE700@GO,0*4D").expect("valid");
let msg = parser.decode(&frame).expect("decoded");
if let AisMessage::BaseStation(report) = msg {
if let Some(h) = report.hour {
assert!(h < 24, "hour sentinel not filtered: {h}");
}
if let Some(m) = report.minute {
assert!(m < 60, "minute sentinel not filtered: {m}");
}
} else {
panic!("expected BaseStation, got {msg:?}");
}
}
}