Skip to content

Commit

Permalink
Merge pull request #23 from salsabiljb/Ais_Message_Types
Browse files Browse the repository at this point in the history
feat: add support for ais message type 6, 10, 12, 13, 14 and 27
  • Loading branch information
squidpickles authored Oct 8, 2024
2 parents c1e7209 + d941534 commit 175072f
Show file tree
Hide file tree
Showing 11 changed files with 655 additions and 12 deletions.
100 changes: 100 additions & 0 deletions src/messages/addressed_safety_related.rs
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");
}
}
124 changes: 124 additions & 0 deletions src/messages/binary_addressed.rs
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);
}
}
4 changes: 2 additions & 2 deletions src/messages/binary_broadcast_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ impl AssignedMode {
}
}

fn parse_base<'a> (data: &'a [u8]) -> IResult<&'a [u8], BinaryBroadcastMessage> {
bits (move |data: (&'a [u8], usize)| -> IResult<_, _> {
fn parse_base<'a>(data: &'a [u8]) -> IResult<&'a [u8], BinaryBroadcastMessage> {
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)?;
Expand Down
2 changes: 1 addition & 1 deletion src/messages/data_link_management_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl<'a> AisMessageType<'a> for DataLinkManagementMessage {
}
}

fn parse_base<'a> (data: &'a [u8]) -> IResult<&'a [u8], DataLinkManagementMessage> {
fn parse_base<'a>(data: &'a [u8]) -> IResult<&'a [u8], DataLinkManagementMessage> {
bits(move |data: (&'a [u8], usize)| -> IResult<_, _> {
let (data, message_type) = take_bits(6u8)(data)?;
let (data, repeat_indicator) = take_bits(2u8)(data)?;
Expand Down
151 changes: 151 additions & 0 deletions src/messages/long_range_ais_broadcast.rs
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);
}
}
Loading

0 comments on commit 175072f

Please sign in to comment.