forked from squidpickles/ais
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for ais message type 6, 10, 12, 13, 14 and 27
- Loading branch information
1 parent
292f6dd
commit d23349e
Showing
8 changed files
with
626 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
//! Addressed Safety-Related Message (type 12) | ||
use super::parsers::*; | ||
use super::AisMessageType; | ||
use crate::errors::Result; | ||
use nom::bits::{bits, complete::take as take_bits}; | ||
use nom::combinator::map; | ||
use nom::IResult; | ||
|
||
#[derive(Debug, PartialEq)] | ||
pub struct AddressedSafetyRelatedMessage { | ||
pub message_type: u8, | ||
pub repeat_indicator: u8, | ||
pub mmsi: u32, | ||
pub seqno: u8, | ||
pub dest_mmsi: u32, | ||
pub retransmit: bool, | ||
pub text: String, | ||
} | ||
|
||
impl<'a> AisMessageType<'a> for AddressedSafetyRelatedMessage { | ||
fn name(&self) -> &'static str { | ||
"Addressed Safety-Related Message" | ||
} | ||
|
||
fn parse(data: &'a [u8]) -> Result<Self> { | ||
let (_, report) = parse_base(data)?; | ||
Ok(report) | ||
} | ||
} | ||
|
||
fn parse_base(data: &[u8]) -> IResult<&[u8], AddressedSafetyRelatedMessage> { | ||
bits(move |data| -> IResult<_, _> { | ||
let (data, message_type) = take_bits(6u8)(data)?; | ||
let (data, repeat_indicator) = take_bits(2u8)(data)?; | ||
let (data, mmsi) = take_bits(30u32)(data)?; | ||
let (data, seqno) = take_bits(2u8)(data)?; | ||
let (data, dest_mmsi) = take_bits(30u32)(data)?; | ||
let (data, retransmit) = map(take_bits(1u8), u8_to_bool)(data)?; | ||
let (data, _spare) = take_bits::<_, u8, _, _>(1u8)(data)?; | ||
|
||
// Ensure there are enough bits remaining for text | ||
let remaining_bits = remaining_bits(data); | ||
if remaining_bits < 6 { | ||
return Err(nom::Err::Error(nom::error::Error::new( | ||
data, | ||
nom::error::ErrorKind::Eof, | ||
))); | ||
} | ||
|
||
let (data, text) = parse_6bit_ascii(data, remaining_bits)?; | ||
|
||
Ok(( | ||
data, | ||
AddressedSafetyRelatedMessage { | ||
message_type, | ||
repeat_indicator, | ||
mmsi, | ||
seqno, | ||
dest_mmsi, | ||
retransmit, | ||
text, | ||
}, | ||
)) | ||
})(data) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_type12_example_a() { | ||
let bytestream = b"<5?SIj1;GbD07??4"; | ||
let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); | ||
let report = AddressedSafetyRelatedMessage::parse(bitstream.as_ref()).unwrap(); | ||
|
||
assert_eq!(report.message_type, 12); | ||
assert_eq!(report.repeat_indicator, 0); | ||
assert_eq!(report.mmsi, 351853000); | ||
assert_eq!(report.seqno, 0); | ||
assert_eq!(report.dest_mmsi, 316123456); | ||
assert_eq!(report.retransmit, false); | ||
assert_eq!(report.text, "GOOD"); | ||
} | ||
|
||
#[test] | ||
fn test_type12_example_b() { | ||
let bytestream = b"<42Lati0W:Ov=C7P6B?=Pjoihhjhqq0"; | ||
let bitstream = crate::messages::unarmor(bytestream, 2).unwrap(); | ||
let report = AddressedSafetyRelatedMessage::parse(bitstream.as_ref()).unwrap(); | ||
|
||
assert_eq!(report.message_type, 12); | ||
assert_eq!(report.repeat_indicator, 0); | ||
assert_eq!(report.mmsi, 271002099); | ||
assert_eq!(report.seqno, 0); | ||
assert_eq!(report.dest_mmsi, 271002111); | ||
assert_eq!(report.retransmit, true); | ||
assert_eq!(report.text, "MSG FROM 271002099"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
//! Binary Addressed Message (type 6) | ||
use super::parsers::{remaining_bits, u8_to_bool}; | ||
use super::AisMessageType; | ||
use crate::errors::Result; | ||
use nom::bits::{bits, complete::take as take_bits}; | ||
use nom::combinator::map; | ||
use nom::IResult; | ||
|
||
#[derive(Debug, PartialEq)] | ||
pub struct BinaryAddressedMessage { | ||
pub message_type: u8, | ||
pub repeat_indicator: u8, | ||
pub mmsi: u32, | ||
pub seqno: u8, | ||
pub dest_mmsi: u32, | ||
pub retransmit: bool, | ||
pub dac: u16, | ||
pub fid: u8, | ||
pub data: Vec<u8>, | ||
} | ||
|
||
impl<'a> AisMessageType<'a> for BinaryAddressedMessage { | ||
fn name(&self) -> &'static str { | ||
"Binary Addressed Message" | ||
} | ||
|
||
fn parse(data: &'a [u8]) -> Result<Self> { | ||
let (_, report) = parse_base(data)?; | ||
Ok(report) | ||
} | ||
} | ||
|
||
fn parse_base(data: &[u8]) -> IResult<&[u8], BinaryAddressedMessage> { | ||
bits(move |data| -> IResult<_, _> { | ||
let (data, message_type) = take_bits(6u8)(data)?; | ||
let (data, repeat_indicator) = take_bits(2u8)(data)?; | ||
let (data, mmsi) = take_bits(30u32)(data)?; | ||
let (data, seqno) = take_bits(2u8)(data)?; | ||
let (data, dest_mmsi) = take_bits(30u32)(data)?; | ||
let (data, retransmit) = map(take_bits(1u8), u8_to_bool)(data)?; | ||
let (data, _spare) = take_bits::<_, u8, _, _>(1u8)(data)?; | ||
let (data, dac) = take_bits(10u16)(data)?; | ||
let (data, fid) = take_bits(6u8)(data)?; | ||
|
||
// Handle the remaining bits for `data` as a Vec<u8> | ||
let remaining_bits = remaining_bits(data); | ||
let mut payload = Vec::with_capacity(remaining_bits / 8); | ||
let mut current_data = data; | ||
|
||
for _ in 1..(remaining_bits / 8) { | ||
let (next_data, byte) = take_bits(8u8)(current_data)?; | ||
payload.push(byte); | ||
current_data = next_data; | ||
} | ||
|
||
Ok(( | ||
current_data, | ||
BinaryAddressedMessage { | ||
message_type, | ||
repeat_indicator, | ||
mmsi, | ||
seqno, | ||
dest_mmsi, | ||
retransmit, | ||
dac, | ||
fid, | ||
data: payload, | ||
}, | ||
)) | ||
})(data) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_type6_example_1() { | ||
let bytestream = b"6B?n;be:cbapalgc;i6?Ow4"; | ||
let bitstream = crate::messages::unarmor(bytestream, 2).unwrap(); | ||
let report = BinaryAddressedMessage::parse(bitstream.as_ref()).unwrap(); | ||
|
||
assert_eq!(report.message_type, 6); | ||
assert_eq!(report.repeat_indicator, 1); | ||
assert_eq!(report.mmsi, 150834090); | ||
assert_eq!(report.seqno, 3); | ||
assert_eq!(report.dest_mmsi, 313240222); | ||
assert_eq!(report.retransmit, false); | ||
assert_eq!(report.dac, 669); | ||
assert_eq!(report.fid, 11); | ||
assert_eq!(report.data, vec![0xeb, 0x2f, 0x11, 0x8f, 0x7f, 0xf1]); | ||
} | ||
|
||
#[test] | ||
fn test_type6_example_2() { | ||
let bytestream = b"6>jR0600V:C0>da4P106P00"; | ||
let bitstream = crate::messages::unarmor(bytestream, 2).unwrap(); | ||
let report = BinaryAddressedMessage::parse(bitstream.as_ref()).unwrap(); | ||
|
||
assert_eq!(report.message_type, 6); | ||
assert_eq!(report.repeat_indicator, 0); | ||
assert_eq!(report.mmsi, 992509976); | ||
assert_eq!(report.seqno, 0); | ||
assert_eq!(report.dest_mmsi, 2500912); | ||
assert_eq!(report.retransmit, false); | ||
assert_eq!(report.dac, 235); | ||
assert_eq!(report.fid, 10); | ||
assert_eq!(report.data, vec![0x44, 0x80, 0x10, 0x06, 0x80, 0x00]); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
//! Long Range AIS Broadcast message (type 27) | ||
use super::navigation::*; | ||
use super::parsers::*; | ||
use super::position_report::NavigationStatus; | ||
use super::AisMessageType; | ||
use crate::errors::Result; | ||
use nom::bits::{bits, complete::take as take_bits}; | ||
use nom::combinator::map; | ||
use nom::IResult; | ||
|
||
#[derive(Debug, PartialEq)] | ||
pub struct LongRangeAisBroadcastMessage { | ||
pub message_type: u8, | ||
pub repeat_indicator: u8, | ||
pub mmsi: u32, | ||
pub position_accuracy: Accuracy, | ||
pub raim: bool, | ||
pub navigation_status: Option<NavigationStatus>, | ||
pub longitude: Option<f32>, | ||
pub latitude: Option<f32>, | ||
pub speed_over_ground: Option<f32>, | ||
pub course_over_ground: Option<f32>, | ||
pub gnss_position_status: bool, | ||
} | ||
|
||
impl<'a> AisMessageType<'a> for LongRangeAisBroadcastMessage { | ||
fn name(&self) -> &'static str { | ||
"Long Range AIS Broadcast message" | ||
} | ||
|
||
fn parse(data: &[u8]) -> Result<Self> { | ||
let (_, report) = parse_base(data)?; | ||
Ok(report) | ||
} | ||
} | ||
|
||
fn parse_base(data: &[u8]) -> IResult<&[u8], LongRangeAisBroadcastMessage> { | ||
bits(move |data| -> IResult<_, _> { | ||
let (data, message_type) = take_bits(6u8)(data)?; | ||
let (data, repeat_indicator) = take_bits(2u8)(data)?; | ||
let (data, mmsi) = take_bits(30u32)(data)?; | ||
let (data, position_accuracy) = map(take_bits(1u8), Accuracy::parse)(data)?; | ||
let (data, raim) = map(take_bits(1u8), u8_to_bool)(data)?; | ||
let (data, navigation_status) = map(take_bits(4u8), NavigationStatus::parse)(data)?; | ||
|
||
let (data, longitude) = map(|data| signed_i32(data, 18), |lon| { | ||
parse_longitude(lon).map(|val| { | ||
if message_type == 27 { | ||
val * 1000.0 | ||
} else { | ||
val | ||
} | ||
}) | ||
})(data)?; | ||
|
||
let (data, latitude) = map(|data| signed_i32(data, 17), |lat| { | ||
parse_latitude(lat).map(|val| { | ||
if message_type == 27 { | ||
val * 1000.0 | ||
} else { | ||
val | ||
} | ||
}) | ||
})(data)?; | ||
|
||
let (data, speed_over_ground) = map(take_bits(6u16), parse_speed_over_ground_62)(data)?; | ||
let (data, course_over_ground) = map(take_bits(9u16), parse_cog_511)(data)?; | ||
let (data, gnss_position_status) = map(take_bits(1u8), u8_to_bool)(data)?; | ||
|
||
Ok(( | ||
data, | ||
LongRangeAisBroadcastMessage { | ||
message_type, | ||
repeat_indicator, | ||
mmsi, | ||
position_accuracy, | ||
raim, | ||
navigation_status, | ||
longitude, | ||
latitude, | ||
speed_over_ground, | ||
course_over_ground, | ||
gnss_position_status, | ||
}, | ||
)) | ||
})(data) | ||
} | ||
|
||
|
||
/// Parse the speed over ground for Long Range AIS Broadcast Message (type 27) | ||
fn parse_speed_over_ground_62(data: u16) -> Option<f32> { | ||
match data { | ||
63 => None, // Speed not available | ||
_ => Some(data as f32), // Speed in knots (0-62) | ||
} | ||
} | ||
|
||
/// Parse the course over ground for Long Range AIS Broadcast Message (type 27) | ||
fn parse_cog_511(data: u16) -> Option<f32> { | ||
match data { | ||
511 => None, // Course not available | ||
_ => Some(data as f32), // Course in degrees (0-359) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
#![allow(clippy::unreadable_literal)] | ||
use crate::test_helpers::f32_equal_naive; | ||
|
||
use super::*; | ||
// use crate::test_helpers::*; | ||
#[test] | ||
fn test_type27_example() { | ||
let bytestream = b"KC5E2b@U19PFdLbMuc5=ROv62<7m"; | ||
let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); | ||
let report = LongRangeAisBroadcastMessage::parse(bitstream.as_ref()).unwrap(); | ||
|
||
assert_eq!(report.message_type, 27); | ||
assert_eq!(report.repeat_indicator, 1); | ||
assert_eq!(report.mmsi, 206914217); | ||
assert_eq!(report.position_accuracy, Accuracy::Unaugmented); | ||
assert_eq!(report.raim, false); | ||
assert_eq!( | ||
report.navigation_status, | ||
Some(NavigationStatus::NotUnderCommand) | ||
); | ||
f32_equal_naive(report.longitude.unwrap(), 137.023333); | ||
f32_equal_naive(report.latitude.unwrap(), 4.84); | ||
assert_eq!(report.speed_over_ground, Some(57.0)); | ||
assert_eq!(report.course_over_ground, Some(167.0)); | ||
assert_eq!(report.gnss_position_status, false); | ||
} | ||
|
||
#[test] | ||
fn test_type27_signed_example() { | ||
let bytestream = b"K01;FQh?PbtE3P00"; | ||
let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); | ||
let report = LongRangeAisBroadcastMessage::parse(bitstream.as_ref()).unwrap(); | ||
|
||
assert_eq!(report.message_type, 27); | ||
assert_eq!(report.mmsi, 1234567); | ||
f32_equal_naive(report.longitude.unwrap(), -13.368334); | ||
f32_equal_naive(report.latitude.unwrap(), -50.121665); | ||
} | ||
} |
Oops, something went wrong.