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.
Merge pull request squidpickles#23 from salsabiljb/Ais_Message_Types
feat: add support for ais message type 6, 10, 12, 13, 14 and 27
- Loading branch information
Showing
11 changed files
with
655 additions
and
12 deletions.
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,100 @@ | ||
//! 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: AsciiString, | ||
} | ||
|
||
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,124 @@ | ||
//! Binary Addressed Message (type 6) | ||
use super::parsers::u8_to_bool; | ||
use super::AisMessageType; | ||
use crate::errors::Result; | ||
use crate::lib; | ||
use nom::bits::{bits, complete::take as take_bits}; | ||
use nom::combinator::map; | ||
use nom::IResult; | ||
|
||
#[cfg(all(not(feature = "std"), not(feature = "alloc")))] | ||
const MAX_DATA_SIZE_BYTES: usize = 119; | ||
|
||
#[cfg(any(feature = "std", feature = "alloc"))] | ||
pub type MessageData = lib::std::vec::Vec<u8>; | ||
#[cfg(all(not(feature = "std"), not(feature = "alloc")))] | ||
pub type MessageData = lib::std::vec::Vec<u8, MAX_DATA_SIZE_BYTES>; | ||
|
||
#[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: MessageData, | ||
} | ||
|
||
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<'a>(data: &'a [u8]) -> IResult<&'a [u8], BinaryAddressedMessage> { | ||
bits(move |data: (&'a [u8], usize)| -> 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)?; | ||
#[cfg(any(feature = "std", feature = "alloc"))] | ||
let data_owned = data.0.into(); | ||
#[cfg(all(not(feature = "std"), not(feature = "alloc")))] | ||
let data_owned = data.0.try_into().map_err(|_| { | ||
nom::Err::Failure(nom::error::Error::new( | ||
data, | ||
nom::error::ErrorKind::TooLarge, | ||
)) | ||
})?; | ||
Ok(( | ||
(<&[u8]>::default(), 0), | ||
BinaryAddressedMessage { | ||
message_type, | ||
repeat_indicator, | ||
mmsi, | ||
seqno, | ||
dest_mmsi, | ||
retransmit, | ||
dac, | ||
fid, | ||
data: data_owned, | ||
}, | ||
)) | ||
})(data) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
#![allow(clippy::unreadable_literal)] | ||
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); | ||
|
||
let expected_data: MessageData = [0xeb, 0x2f, 0x11, 0x8f, 0x7f, 0xf1, 0x00] | ||
.into_iter() | ||
.collect(); | ||
assert_eq!(report.data, expected_data); | ||
} | ||
|
||
#[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); | ||
|
||
let expected_data: MessageData = [0x44, 0x80, 0x10, 0x06, 0x80, 0x00, 0x00] | ||
.into_iter() | ||
.collect(); | ||
assert_eq!(report.data, expected_data); | ||
} | ||
} |
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
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
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,151 @@ | ||
//! 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.