From cdd7b6efe843378396ccc83a87f32c10b55f1bba Mon Sep 17 00:00:00 2001 From: Kevin Rauwolf Date: Fri, 16 Sep 2022 22:02:07 -0700 Subject: [PATCH] Added no_std and alloc. Closes #9 --- Cargo.toml | 13 ++- src/bin/{ais.rs => aisparser.rs} | 10 +- src/errors.rs | 108 ++++++++++++------ src/errors_nostd.rs | 66 +++++++++++ src/lib.rs | 46 +++++++- src/messages/aid_to_navigation_report.rs | 6 +- src/messages/base_station_report.rs | 6 +- src/messages/binary_broadcast_message.rs | 26 ++++- src/messages/data_link_management_message.rs | 18 ++- .../dgnss_broadcast_binary_message.rs | 26 ++++- .../extended_class_b_position_report.rs | 6 +- src/messages/group_assignment_command.rs | 5 +- src/messages/interrogation.rs | 35 ++++-- src/messages/mod.rs | 41 ++++++- src/messages/navigation.rs | 2 +- src/messages/nom_noalloc.rs | 95 +++++++++++++++ src/messages/parsers.rs | 75 +++++++++--- src/messages/position_report.rs | 8 +- .../standard_class_b_position_report.rs | 2 +- .../static_and_voyage_related_data.rs | 16 +-- src/messages/static_data_report.rs | 19 +-- src/messages/utc_date_response.rs | 2 +- src/sentence.rs | 83 ++++++++++---- 23 files changed, 576 insertions(+), 138 deletions(-) rename src/bin/{ais.rs => aisparser.rs} (77%) create mode 100644 src/errors_nostd.rs create mode 100644 src/messages/nom_noalloc.rs diff --git a/Cargo.toml b/Cargo.toml index 047524e..21d11c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,18 @@ categories = ["parser-implementations"] license = "Apache-2.0" edition = "2021" +[features] +std = ["nom/std"] +alloc = ["nom/alloc"] +default = ["std"] + [dependencies] -thiserror = "1" -nom = "7" +nom = { version = "7", default-features = false } +heapless = { version = "0.7" } + +[[bin]] +name = "aisparser" +required-features = ["std"] [badges] travis-ci = { repository = "squidpickles/ais", branch = "master" } diff --git a/src/bin/ais.rs b/src/bin/aisparser.rs similarity index 77% rename from src/bin/ais.rs rename to src/bin/aisparser.rs index cf56dfa..614df94 100644 --- a/src/bin/ais.rs +++ b/src/bin/aisparser.rs @@ -1,14 +1,16 @@ +use ais::lib; + use ais::sentence::{AisFragments, AisParser}; -use std::io::BufRead; +use lib::std::io::BufRead; -use std::io; +use lib::std::io; fn parse_nmea_line(parser: &mut AisParser, line: &[u8]) -> Result<(), ais::errors::Error> { let sentence = parser.parse(line, true)?; if let AisFragments::Complete(sentence) = sentence { println!( "{:?}\t{:?}", - std::str::from_utf8(line).unwrap(), + lib::std::str::from_utf8(line).unwrap(), sentence.message ); } @@ -26,7 +28,7 @@ fn main() { .map(|line| line.unwrap()) .for_each(|line| { parse_nmea_line(&mut parser, &line).unwrap_or_else(|err| { - eprintln!("{:?}\t{:?}", std::str::from_utf8(&line).unwrap(), err); + eprintln!("{:?}\t{:?}", lib::std::str::from_utf8(&line).unwrap(), err); }); }); } diff --git a/src/errors.rs b/src/errors.rs index f8b97bb..18c68e5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,50 +1,90 @@ //! Custom error types used by this crate -use thiserror::Error; - -/// Custom `Result` to prepopulate `Error` type -pub type Result = std::result::Result; - -/// A general error in parsing an AIS message -#[derive(Error, Debug)] -pub enum Error { - #[error("invalid NMEA sentence: '{msg}'")] - Nmea { msg: String }, - #[error("checksum mismatch; expected: {expected:#X}, received: {found:#X}")] - Checksum { expected: u8, found: u8 }, -} -impl From<&str> for Error { - fn from(err: &str) -> Self { - Self::Nmea { msg: err.into() } +pub use err::*; + +#[cfg(any(feature = "std", feature = "alloc"))] +mod err { + use crate::lib; + use lib::std::format; + use lib::std::string::{String, ToString}; + + /// Custom `Result` to prepopulate `Error` type + pub type Result = lib::std::result::Result; + /// A general error in parsing an AIS message + #[derive(Debug)] + pub enum Error { + //#[error("invalid NMEA sentence: '{msg}'")] + Nmea { msg: String }, + //#[error("checksum mismatch; expected: {expected:#X}, received: {found:#X}")] + Checksum { expected: u8, found: u8 }, + } + + impl From<&str> for Error { + fn from(err: &str) -> Self { + Self::Nmea { msg: err.into() } + } } -} -impl From for Error { - fn from(err: String) -> Self { - Self::Nmea { msg: err } + impl From for Error { + fn from(err: String) -> Self { + Self::Nmea { msg: err } + } } -} -impl From> for Error { - fn from(err: nom::Err<&[u8]>) -> Self { - Self::Nmea { - msg: err.to_string(), + impl From> for Error { + fn from(err: nom::Err<&[u8]>) -> Self { + Self::Nmea { + msg: err.to_string(), + } } } -} -impl From> for Error { - fn from(err: nom::Err<(&[u8], nom::error::ErrorKind)>) -> Self { - Self::Nmea { - msg: err.to_string(), + impl From> for Error { + fn from(err: nom::Err<(&[u8], nom::error::ErrorKind)>) -> Self { + Self::Nmea { + msg: err.to_string(), + } + } + } + + impl From>> for Error { + fn from(err: nom::Err>) -> Self { + Self::Nmea { + msg: format!("{:?}", err), + } } } } -impl From>> for Error { - fn from(err: nom::Err>) -> Self { - Self::Nmea { - msg: format!("{:?}", err), +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +mod err { + use crate::lib; + + /// Custom `Result` to prepopulate `Error` type + pub type Result = lib::std::result::Result; + /// A general error in parsing an AIS message + #[derive(Debug)] + pub enum Error { + //#[error("invalid NMEA sentence: '{msg}'")] + Nmea { msg: &'static str }, + //#[error("checksum mismatch; expected: {expected:#X}, received: {found:#X}")] + Checksum { expected: u8, found: u8 }, + } + + impl From<&'static str> for Error { + fn from(err: &'static str) -> Self { + Self::Nmea { msg: err } + } + } + + impl From> for Error { + fn from(err: nom::Err) -> Self { + let err_str = match err { + nom::Err::Incomplete(_) => "Incomplete data", + nom::Err::Error(_) => "Parser error", + nom::Err::Failure(_) => "Parser unrecoverable failure", + }; + Self::Nmea { msg: err_str } } } } diff --git a/src/errors_nostd.rs b/src/errors_nostd.rs new file mode 100644 index 0000000..d677c0c --- /dev/null +++ b/src/errors_nostd.rs @@ -0,0 +1,66 @@ +//! Custom error types used by this crate +use crate::lib; +use lib::std::format; +use lib::std::string::{String, ToString}; + +/// Custom `Result` to prepopulate `Error` type +pub type Result = lib::std::result::Result; +/// A general error in parsing an AIS message +#[derive(Debug)] +pub enum Error { + //#[error("invalid NMEA sentence: '{msg}'")] + Nmea { msg: &'static str }, + //#[error("checksum mismatch; expected: {expected:#X}, received: {found:#X}")] + Checksum { expected: u8, found: u8 }, +} + +impl From<&'static str> for Error { + fn from(err: &str) -> Self { + Self::Nmea { msg: err } + } +} + +impl From for Error { + fn from(err: String) -> Self { + Self::Nmea { msg: err } + } +} + +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +impl From> for Error { + fn from(err: nom::Err) -> Self { + let err_str = match err { + nom::Err::Incomplete(_) => "Incomplete data", + nom::Err::Error(_) => "Parser error", + nom::Err::Failure(_) => "Parser unrecoverable failure", + }; + Self::Nmea { msg: err_str } + } +} + +impl From> for Error { + fn from(err: nom::Err) -> Self { + let err_str = match err { + nom::Err::Incomplete(_) => "Incomplete data", + nom::Err::Error(_) => "Parser error", + nom::Err::Failure(_) => "Parser unrecoverable failure", + }; + Self::Nmea { msg: err_str } + } +} + +impl From> for Error { + fn from(err: nom::Err<(&[u8], nom::error::ErrorKind)>) -> Self { + Self::Nmea { + msg: err.to_string(), + } + } +} + +impl From>> for Error { + fn from(err: nom::Err>) -> Self { + Self::Nmea { + msg: format!("{:?}", err), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ca894e6..1f6e576 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,18 +30,62 @@ //! } //! # Ok::<(), ais::errors::Error>(()) //! ``` +#![cfg_attr(not(feature = "std"), no_std)] + +#[doc(hidden)] +/// standard library stuff available crate-wide, regardless of `no_std` state +pub mod lib { + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + pub mod std { + pub use core::{borrow, cmp, fmt, mem, result, str}; + + pub mod vec { + pub use heapless::Vec; + } + + pub mod string { + pub use heapless::String; + } + + pub trait Error: fmt::Debug + fmt::Display { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } + } + } + #[cfg(all(not(feature = "std"), feature = "alloc"))] + pub mod std { + extern crate alloc; + pub use alloc::{borrow, fmt, format, str, string, vec}; + pub use core::{cmp, mem, result}; + + pub trait Error: fmt::Debug + fmt::Display { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } + } + } + + #[cfg(feature = "std")] + pub mod std { + #[doc(hidden)] + pub use std::{borrow, cmp, error, fmt, format, io, mem, result, str, string, vec}; + } +} + pub mod errors; pub mod messages; pub mod sentence; pub use errors::Result; pub use sentence::{AisFragments, AisParser}; + #[cfg(test)] mod test_helpers { #[inline] /// Compares two `f32`s, assuming they are both numeric, and panics if they differ pub fn f32_equal_naive(a: f32, b: f32) { - if (a - b).abs() >= std::f32::EPSILON { + if (a - b).abs() >= f32::EPSILON { panic!("float {} != {}", a, b); } } diff --git a/src/messages/aid_to_navigation_report.rs b/src/messages/aid_to_navigation_report.rs index 48af01e..eb3dd86 100644 --- a/src/messages/aid_to_navigation_report.rs +++ b/src/messages/aid_to_navigation_report.rs @@ -90,7 +90,7 @@ pub struct AidToNavigationReport { pub repeat_indicator: u8, pub mmsi: u32, pub aid_type: Option, - pub name: String, + pub name: AsciiString, pub accuracy: Accuracy, pub longitude: Option, pub latitude: Option, @@ -112,7 +112,7 @@ impl<'a> AisMessageType<'a> for AidToNavigationReport { "Aid to Navigation Report" } - fn parse(data: &[u8]) -> Result { + fn parse(data: &'a [u8]) -> Result { let (_, report) = parse_message(data)?; Ok(report) } @@ -177,7 +177,7 @@ mod tests { fn test_type21_not_extended() { let bytestream = b"E>kb9II9S@0`8@:9ah;0TahIW@@;Uafb:r5Ih00003vP100"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let message = AidToNavigationReport::parse(&bitstream).unwrap(); + let message = AidToNavigationReport::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.message_type, 21); assert_eq!(message.repeat_indicator, 0); assert_eq!(message.mmsi, 993692005); diff --git a/src/messages/base_station_report.rs b/src/messages/base_station_report.rs index e29cfde..d76dc72 100644 --- a/src/messages/base_station_report.rs +++ b/src/messages/base_station_report.rs @@ -92,7 +92,7 @@ mod tests { fn test_type4() { let bytestream = b"403OtVAv7=i?;o?IaHE`4Iw020S:"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let message = BaseStationReport::parse(&bitstream).unwrap(); + let message = BaseStationReport::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.message_type, 4); assert_eq!(message.repeat_indicator, 0); assert_eq!(message.mmsi, 003669145); @@ -120,7 +120,7 @@ mod tests { fn test_type4_2() { let bytestream = b"403OviQuMGCqWrRO9>E6fE700@GO"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let message = BaseStationReport::parse(&bitstream).unwrap(); + let message = BaseStationReport::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.message_type, 4); assert_eq!(message.repeat_indicator, 0); assert_eq!(message.mmsi, 3669702); @@ -148,7 +148,7 @@ mod tests { fn test_type4_invalid_date() { let bytestream = b"4h2E:qT47wk?0; +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +pub type MessageData = lib::std::vec::Vec; + #[derive(Debug, PartialEq, Eq)] pub struct BinaryBroadcastMessage { pub message_type: u8, @@ -13,7 +22,7 @@ pub struct BinaryBroadcastMessage { pub dac: u16, /// Functional ID pub fid: u8, - pub data: Vec, + pub data: MessageData, } impl<'a> AisMessageType<'a> for BinaryBroadcastMessage { @@ -69,6 +78,15 @@ fn parse_base(data: &[u8]) -> IResult<&[u8], BinaryBroadcastMessage> { let (data, _spare) = take_bits::<_, u8, _, _>(2u8)(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), BinaryBroadcastMessage { @@ -77,7 +95,7 @@ fn parse_base(data: &[u8]) -> IResult<&[u8], BinaryBroadcastMessage> { mmsi, dac, fid, - data: data.0.to_vec(), + data: data_owned, }, )) })(data) @@ -93,7 +111,7 @@ mod tests { // !AIVDM,1,1,,A,8@2; +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +pub type SlotReservationList = lib::std::vec::Vec; + #[derive(Debug, PartialEq, Eq)] pub struct DataLinkManagementMessage { pub message_type: u8, pub repeat_indicator: u8, pub mmsi: u32, - pub reservations: Vec, + pub reservations: SlotReservationList, } impl<'a> AisMessageType<'a> for DataLinkManagementMessage { @@ -56,7 +65,10 @@ fn parse_base(data: &[u8]) -> IResult<&[u8], DataLinkManagementMessage> { let (data, repeat_indicator) = take_bits(2u8)(data)?; let (data, mmsi) = take_bits(30u32)(data)?; let (data, _spare) = take_bits::<_, u8, _, _>(2u8)(data)?; + #[cfg(any(feature = "std", feature = "alloc"))] let (data, reservations) = many_m_n(1, 4, SlotReservation::parse)(data)?; + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + let (data, reservations) = many_m_n::<_, _, _, _, 4>(1, SlotReservation::parse)(data)?; Ok(( data, DataLinkManagementMessage { @@ -78,7 +90,7 @@ mod tests { fn test_2_slots() { let bytestream = b"D02; +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +pub type CorrectionData = lib::std::vec::Vec; + #[derive(Debug, PartialEq)] pub struct DgnssBroadcastBinaryMessage { pub message_type: u8, @@ -24,7 +33,7 @@ pub struct DifferentialCorrectionData { pub sequence_number: u8, pub n: u8, pub health: u8, - pub data: Vec, + pub data: CorrectionData, } impl DifferentialCorrectionData { @@ -35,6 +44,15 @@ impl DifferentialCorrectionData { let (data, sequence_number) = take_bits(3u8)(data)?; let (data, n) = take_bits(5u8)(data)?; let (data, health) = take_bits(3u8)(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), Self { @@ -44,7 +62,7 @@ impl DifferentialCorrectionData { sequence_number, n, health, - data: data.0.to_vec(), + data: data_owned, }, )) } @@ -55,7 +73,7 @@ impl<'a> AisMessageType<'a> for DgnssBroadcastBinaryMessage { "DGNSS Broadcast Binary Message" } - fn parse(data: &[u8]) -> Result { + fn parse(data: &'a [u8]) -> Result { let (_, message) = parse_base(data)?; Ok(message) } @@ -110,7 +128,7 @@ mod tests { let bytestream = b"A02VqLPA4I6C07h5Ed1h, pub true_heading: Option, pub timestamp: u8, - pub name: String, + pub name: AsciiString, pub type_of_ship_and_cargo: Option, pub dimension_to_bow: u16, pub dimension_to_stern: u16, @@ -40,7 +40,7 @@ impl<'a> AisMessageType<'a> for ExtendedClassBPositionReport { "Extended Class B Position Report" } - fn parse(data: &[u8]) -> Result { + fn parse(data: &'a [u8]) -> Result { let (_, report) = parse_base(data)?; Ok(report) } @@ -109,7 +109,7 @@ mod tests { fn test_position() { let bytestream = b"C6:ijoP00:9NNF4TEspILDN0Vc0jNc1WWV0000000000S2<6R20P"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let report = ExtendedClassBPositionReport::parse(&bitstream).unwrap(); + let report = ExtendedClassBPositionReport::parse(bitstream.as_ref()).unwrap(); assert_eq!(report.message_type, 19); assert_eq!(report.repeat_indicator, 0); assert_eq!(report.mmsi, 413954782); diff --git a/src/messages/group_assignment_command.rs b/src/messages/group_assignment_command.rs index 1aa6897..f7be86d 100644 --- a/src/messages/group_assignment_command.rs +++ b/src/messages/group_assignment_command.rs @@ -1,5 +1,6 @@ //! Base Station Report (type 4) -use std::time::Duration; +use crate::lib; +use lib::std::time::Duration; use super::navigation::*; use super::parsers::*; @@ -161,7 +162,7 @@ mod tests { fn test_type23() { let bytestream = b"G02OHAP8aLvg@@b1tF600000;00"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let message = GroupAssignmentCommand::parse(&bitstream).unwrap(); + let message = GroupAssignmentCommand::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.message_type, 23); assert_eq!(message.repeat_indicator, 0); assert_eq!(message.mmsi, 2611270); diff --git a/src/messages/interrogation.rs b/src/messages/interrogation.rs index 83de9a2..6644512 100644 --- a/src/messages/interrogation.rs +++ b/src/messages/interrogation.rs @@ -1,7 +1,9 @@ //! Interrogation (type 15) use super::parsers::*; +use super::push_unwrap; use super::AisMessageType; use crate::errors::Result; +use crate::lib; use nom::bits::{bits, complete::take as take_bits}; use nom::IResult; @@ -34,38 +36,49 @@ impl Message { } } +#[cfg(any(feature = "std", feature = "alloc"))] +pub type MessageList = lib::std::vec::Vec; +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +pub type MessageList = lib::std::vec::Vec; + #[derive(Debug, PartialEq, Eq)] pub struct Station { pub mmsi: u32, - pub messages: Vec, + pub messages: MessageList, } impl Station { pub fn parse(data: (&[u8], usize)) -> IResult<(&[u8], usize), Self> { let (data, mmsi) = take_bits(30u32)(data)?; - let mut messages = Vec::new(); + let mut messages: MessageList = Default::default(); let (data, message) = Message::parse(data)?; - messages.push(message); + push_unwrap(&mut messages, message); let data = if remaining_bits(data) >= 8 { let (data, _spare) = take_bits::<_, u8, _, _>(2u8)(data)?; let (data, message) = Message::parse(data)?; if message.message_type != 0 || message.slot_offset.is_some() { - messages.push(message); + push_unwrap(&mut messages, message); } data } else { data }; + // TODO: this is only dealing with 2 messages out of 3? Ok((data, Self { mmsi, messages })) } } +#[cfg(any(feature = "std", feature = "alloc"))] +pub type StationList = lib::std::vec::Vec; +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +pub type StationList = lib::std::vec::Vec; + #[derive(Debug, PartialEq, Eq)] pub struct Interrogation { pub message_type: u8, pub repeat_indicator: u8, pub mmsi: u32, - pub stations: Vec, + pub stations: StationList, } impl<'a> AisMessageType<'a> for Interrogation { @@ -85,13 +98,13 @@ fn parse_message(data: &[u8]) -> IResult<&[u8], Interrogation> { let (data, repeat_indicator) = take_bits(2u8)(data)?; let (data, mmsi) = take_bits(30u32)(data)?; let (data, _spare) = take_bits::<_, u8, _, _>(2u8)(data)?; - let mut stations = Vec::new(); + let mut stations: StationList = Default::default(); let (data, station) = Station::parse(data)?; - stations.push(station); + push_unwrap(&mut stations, station); let remaining = remaining_bits(data); let data = if remaining >= 30 { let (data, station) = Station::parse(data)?; - stations.push(station); + push_unwrap(&mut stations, station); take_bits::<_, u8, _, _>(2u8)(data)?.0 } else { (<&[u8]>::default(), 0) @@ -118,7 +131,7 @@ mod tests { fn test_type15_short() { let bytestream = b"?03Owo@nwsI0D00"; let bitstream = crate::messages::unarmor(bytestream, 2).unwrap(); - let message = Interrogation::parse(&bitstream).unwrap(); + let message = Interrogation::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.message_type, 15); assert_eq!(message.repeat_indicator, 0); assert_eq!(message.mmsi, 3669981); @@ -135,7 +148,7 @@ mod tests { fn test_type15_busy() { let bytestream = b"?>eq`dAh3`TQP00"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let message = Interrogation::parse(&bitstream).unwrap(); + let message = Interrogation::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.message_type, 15); assert_eq!(message.repeat_indicator, 0); assert_eq!(message.mmsi, 987654321); @@ -152,7 +165,7 @@ mod tests { fn test_type15_longer() { let bytestream = b"?04759iVhc2lD003000"; let bitstream = crate::messages::unarmor(bytestream, 2).unwrap(); - let message = Interrogation::parse(&bitstream).unwrap(); + let message = Interrogation::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.message_type, 15); assert_eq!(message.repeat_indicator, 0); assert_eq!(message.mmsi, 4310311); diff --git a/src/messages/mod.rs b/src/messages/mod.rs index 6fe3f10..4db5344 100644 --- a/src/messages/mod.rs +++ b/src/messages/mod.rs @@ -1,5 +1,7 @@ //! Specific AIS message types use crate::errors::Result; +use crate::lib; +use crate::sentence::AisRawData; pub mod aid_to_navigation_report; pub mod base_station_report; @@ -9,6 +11,8 @@ pub mod dgnss_broadcast_binary_message; pub mod extended_class_b_position_report; pub mod interrogation; mod navigation; +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +mod nom_noalloc; mod parsers; pub mod position_report; mod radio_status; @@ -20,6 +24,9 @@ pub mod utc_date_response; pub use parsers::message_type; +#[cfg(feature = "alloc")] +use crate::lib::std::{format, vec, vec::Vec}; + /// Contains all structured messages recognized by this crate #[derive(Debug, PartialEq)] pub enum AisMessage { @@ -88,7 +95,10 @@ pub fn parse(unarmored: &[u8]) -> Result { 24 => Ok(AisMessage::StaticDataReport( static_data_report::StaticDataReport::parse(unarmored)?, )), + #[cfg(any(feature = "std", feature = "alloc"))] _ => Err(format!("Unimplemented type: {}", result).into()), + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + _ => Err("Unimplemented message type".into()), } } @@ -106,16 +116,28 @@ pub fn parse(unarmored: &[u8]) -> Result { /// to a valid 6-bit chunk. /// /// See for more details. -pub fn unarmor(data: &[u8], fill_bits: usize) -> Result> { +pub fn unarmor(data: &[u8], fill_bits: usize) -> Result { let bit_count = data.len() * 6; let byte_count = (bit_count / 8) + ((bit_count % 8 != 0) as usize); + #[cfg(any(feature = "std", feature = "alloc"))] let mut output = vec![0; byte_count]; + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + let mut output = { + let mut output = AisRawData::default(); + output + .resize(byte_count, 0) + .map_err(|_| crate::errors::Error::from("Unarmor output vector too large"))?; + output + }; let mut offset = 0; for byte in data { let unarmored = match *byte { 48..=87 => byte - 48, 96..=119 => byte - 56, + #[cfg(any(feature = "std", feature = "alloc"))] _ => return Err(format!("Value out of range: {}", byte).into()), + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + _ => return Err("Armored byte value out of range".into()), } << 2; let offset_byte = offset / 8; let offset_bit = offset % 8; @@ -132,10 +154,11 @@ pub fn unarmor(data: &[u8], fill_bits: usize) -> Result> { 1..=7 => bit_count % 8, _ => unreachable!(), }; - let final_idx = output.len() - 1; + let final_idx = byte_count - 1; { let byte = &mut output[final_idx]; - let shift = (8 - bits_in_final_byte) + std::cmp::min(fill_bits, bits_in_final_byte); + let shift = + (8 - bits_in_final_byte) + lib::std::cmp::min(fill_bits, bits_in_final_byte); *byte &= match shift { 0..=7 => 0xffu8 << shift, 8 => 0x0u8, @@ -150,6 +173,18 @@ pub fn unarmor(data: &[u8], fill_bits: usize) -> Result> { Ok(output) } +#[cfg(any(feature = "std", feature = "alloc"))] +#[inline] +fn push_unwrap(list: &mut Vec, item: T) { + list.push(item); +} + +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +#[inline] +fn push_unwrap(list: &mut lib::std::vec::Vec, item: T) { + list.push(item).unwrap(); +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/messages/navigation.rs b/src/messages/navigation.rs index 0fc9095..4c9439c 100644 --- a/src/messages/navigation.rs +++ b/src/messages/navigation.rs @@ -71,7 +71,7 @@ impl RateOfTurn { pub fn rate(self) -> Option { match self.raw { - -126..=126 => Some((self.raw as f32 / 4.733).powi(2)), + -126..=126 => Some((self.raw as f32 / 4.733) * (self.raw as f32 / 4.733)), -127 => None, 127 => None, _ => unreachable!(), diff --git a/src/messages/nom_noalloc.rs b/src/messages/nom_noalloc.rs new file mode 100644 index 0000000..f8cea2d --- /dev/null +++ b/src/messages/nom_noalloc.rs @@ -0,0 +1,95 @@ +//! Replacements for nom functions missing without the `alloc` feature + +use core::fmt::Debug; + +use crate::lib; +use nom::{ + error::{ErrorKind, ParseError}, + IResult, InputLength, Parser, +}; + +pub fn many_m_n( + min: usize, + mut parse: F, +) -> impl FnMut(I) -> IResult, E> +where + I: Clone + InputLength, + F: Parser, + E: ParseError, +{ + move |mut input: I| { + if min > MAX { + return Err(nom::Err::Failure(E::from_error_kind( + input, + ErrorKind::ManyMN, + ))); + } + + let mut res = crate::lib::std::vec::Vec::new(); + for count in 0..MAX { + let len = input.input_len(); + match parse.parse(input.clone()) { + Ok((tail, value)) => { + // infinite loop check: the parser must always consume + if tail.input_len() == len { + return Err(nom::Err::Error(E::from_error_kind( + input, + ErrorKind::ManyMN, + ))); + } + + // loop is limited by the same const generic as the vector size, + // so `res` will not be full + unsafe { res.push_unchecked(value) }; + input = tail; + } + Err(nom::Err::Error(e)) => { + if count < min { + return Err(nom::Err::Error(E::append(input, ErrorKind::ManyMN, e))); + } else { + return Ok((input, res)); + } + } + Err(e) => { + return Err(e); + } + } + } + + Ok((input, res)) + } +} + +pub fn count( + mut f: F, + count: usize, +) -> impl FnMut(I) -> IResult, E> +where + I: Clone + PartialEq, + F: Parser, + E: ParseError, +{ + debug_assert!(count <= VEC_SIZE); + move |i: I| { + let mut input = i.clone(); + let mut res = crate::lib::std::vec::Vec::new(); + + for _ in 0..count { + let input_ = input.clone(); + match f.parse(input_) { + Ok((i, o)) => { + res.push(o).expect("Pushing item to full Vec"); + input = i; + } + Err(nom::Err::Error(e)) => { + return Err(nom::Err::Error(E::append(i, ErrorKind::Count, e))); + } + Err(e) => { + return Err(e); + } + } + } + + Ok((input, res)) + } +} diff --git a/src/messages/parsers.rs b/src/messages/parsers.rs index 1b8f829..ff4d362 100644 --- a/src/messages/parsers.rs +++ b/src/messages/parsers.rs @@ -1,12 +1,25 @@ //! Common parsers use crate::errors::Result; +use crate::lib; +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +use super::nom_noalloc::count; +use lib::std::string::String; use nom::bits::{bits, complete::take as take_bits}; use nom::combinator::{map, map_res}; use nom::error::ErrorKind; +#[cfg(any(feature = "std", feature = "alloc"))] use nom::multi::count; use nom::IResult; +#[cfg(feature = "alloc")] +use crate::lib::std::{format, string::ToString}; + +/// This is the maximum number of bytes that a 6-bit ASCII representation can turn into. +/// The largest we see anywhere is 120 bits = 20 ASCII bytes. +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +const MAX_6BIT_ARRAY_BYTES: usize = 20; + pub fn parse_year(data: (&[u8], usize)) -> IResult<(&[u8], usize), Option> { map(take_bits(14u16), |year| match year { 0 => None, @@ -44,21 +57,49 @@ pub fn remaining_bits(data: (&[u8], usize)) -> usize { data.0.len() * 8 - data.1 } +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +pub type AsciiString = String; +#[cfg(any(feature = "std", feature = "alloc"))] +pub type AsciiString = String; + /// Converts a number of bits, represented as 6-bit ASCII, into a String -pub fn parse_6bit_ascii(input: (&[u8], usize), size: usize) -> IResult<(&[u8], usize), String> { +pub fn parse_6bit_ascii( + input: (&[u8], usize), + size: usize, +) -> IResult<(&[u8], usize), AsciiString> { let char_count = size / 6; + #[cfg(any(feature = "std", feature = "alloc"))] let (input, bytes) = count(map_res(take_bits(6u8), sixbit_to_ascii), char_count)(input)?; - std::str::from_utf8(&bytes) - .map(|val| { - ( - input, - val.trim_start() - .trim_end_matches('@') - .trim_end() - .to_string(), - ) - }) - .map_err(|_| nom::Err::Failure(nom::error::Error::new(input, ErrorKind::AlphaNumeric))) + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + let (input, bytes) = count::<_, _, _, _, MAX_6BIT_ARRAY_BYTES>( + map_res(take_bits(6u8), sixbit_to_ascii), + char_count, + )(input)?; + #[cfg(any(feature = "std", feature = "alloc"))] + { + lib::std::str::from_utf8(&bytes) + .map(|val| { + ( + input, + val.trim_start() + .trim_end_matches('@') + .trim_end() + .to_string(), + ) + }) + .map_err(|_| nom::Err::Failure(nom::error::Error::new(input, ErrorKind::AlphaNumeric))) + } + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + { + lib::std::str::from_utf8(&bytes) + .map(|val| { + ( + input, + val.trim_start().trim_end_matches('@').trim_end().into(), + ) + }) + .map_err(|_| nom::Err::Failure(nom::error::Error::new(input, ErrorKind::AlphaNumeric))) + } } /// Gets the message type from the first byte of supplied data @@ -75,11 +116,19 @@ pub fn message_type_bits(data: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { #[inline] fn sixbit_to_ascii(data: u8) -> Result { + #[cfg(any(feature = "std", feature = "alloc"))] match data { 0..=31 => Ok(data + 64), 32..=63 => Ok(data), _ => Err(format!("Illegal 6-bit character: {}", data).into()), } + + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + match data { + 0..=31 => Ok(data + 64), + 32..=63 => Ok(data), + _ => Err("Illegal 6-bit character".into()), + } } /// Converts a `0` to `false`, `1` to `true`. Expects only a single bit, so @@ -94,7 +143,7 @@ pub fn u8_to_bool(data: u8) -> bool { } pub fn signed_i32(input: (&[u8], usize), len: usize) -> IResult<(&[u8], usize), i32> { - assert!(len <= ::std::mem::size_of::() * 8); + assert!(len <= lib::std::mem::size_of::() * 8); let (input, num) = take_bits::<_, i32, _, _>(len)(input)?; let mask = !0i32 << len; Ok(( diff --git a/src/messages/position_report.rs b/src/messages/position_report.rs index f809f9e..00f3c1a 100644 --- a/src/messages/position_report.rs +++ b/src/messages/position_report.rs @@ -134,7 +134,7 @@ mod tests { fn test_position() { let bytestream = b"13u?etPv2;0n:dDPwUM1U1Cb069D"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let position = PositionReport::parse(&bitstream).unwrap(); + let position = PositionReport::parse(bitstream.as_ref()).unwrap(); assert_eq!(position.message_type, 1); assert_eq!(position.repeat_indicator, 0); assert_eq!(position.mmsi, 265547250); @@ -172,7 +172,7 @@ mod tests { fn test_type1() { let bytestream = b"16SteH0P00Jt63hHaa6SagvJ087r"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let position = PositionReport::parse(&bitstream).unwrap(); + let position = PositionReport::parse(bitstream.as_ref()).unwrap(); f32_equal_naive(position.longitude.unwrap(), -70.7582); if let RadioStatus::Sotdma(radio_status) = position.radio_status { assert_eq!(radio_status.sync_state, SyncState::UtcDirect); @@ -187,7 +187,7 @@ mod tests { fn test_type3() { let bytestream = b"38Id705000rRVJhE7cl9n;160000"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let position = PositionReport::parse(&bitstream).unwrap(); + let position = PositionReport::parse(bitstream.as_ref()).unwrap(); assert_eq!(position.message_type, 3); assert_eq!(position.mmsi, 563808000); assert_eq!( @@ -213,7 +213,7 @@ mod tests { fn test_maneuver_indicator_out_of_spec() { let bytestream = b"33nQ:B50000FiEBRjpcK19qSR>`<"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let position = PositionReport::parse(&bitstream).unwrap(); + let position = PositionReport::parse(bitstream.as_ref()).unwrap(); assert_eq!( position.maneuver_indicator, Some(ManeuverIndicator::Unknown(3)) diff --git a/src/messages/standard_class_b_position_report.rs b/src/messages/standard_class_b_position_report.rs index 620692e..6352afc 100644 --- a/src/messages/standard_class_b_position_report.rs +++ b/src/messages/standard_class_b_position_report.rs @@ -124,7 +124,7 @@ mod tests { fn test_position() { let bytestream = b"B6:hQDh0029Pt<4TAS003h6TSP00"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let report = StandardClassBPositionReport::parse(&bitstream).unwrap(); + let report = StandardClassBPositionReport::parse(bitstream.as_ref()).unwrap(); assert_eq!(report.message_type, 18); assert_eq!(report.repeat_indicator, 0); assert_eq!(report.mmsi, 413933907); diff --git a/src/messages/static_and_voyage_related_data.rs b/src/messages/static_and_voyage_related_data.rs index fd2bf58..bbe692d 100644 --- a/src/messages/static_and_voyage_related_data.rs +++ b/src/messages/static_and_voyage_related_data.rs @@ -3,6 +3,7 @@ use super::parsers::*; use super::types::*; 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; @@ -14,8 +15,8 @@ pub struct StaticAndVoyageRelatedData { pub mmsi: u32, pub ais_version: u8, pub imo_number: u32, - pub callsign: String, - pub vessel_name: String, + pub callsign: AsciiString, + pub vessel_name: AsciiString, pub ship_type: Option, pub dimension_to_bow: u16, pub dimension_to_stern: u16, @@ -27,7 +28,7 @@ pub struct StaticAndVoyageRelatedData { pub eta_hour_utc: u8, pub eta_minute_utc: Option, pub draught: f32, - pub destination: String, + pub destination: AsciiString, pub dte: Dte, } @@ -36,7 +37,7 @@ impl<'a> AisMessageType<'a> for StaticAndVoyageRelatedData { "Static and Voyage Related Data" } - fn parse(data: &[u8]) -> Result { + fn parse(data: &'a [u8]) -> Result { let (_, report) = parse_message(data)?; Ok(report) } @@ -66,7 +67,8 @@ fn parse_message(data: &[u8]) -> IResult<&[u8], StaticAndVoyageRelatedData> { })(data)?; // Sometimes these messages are truncated. // First, take only up to 120 bits for destination - let (data, destination) = parse_6bit_ascii(data, std::cmp::min(120, remaining_bits(data)))?; + let (data, destination) = + parse_6bit_ascii(data, lib::std::cmp::min(120, remaining_bits(data)))?; // Second, if there are no bits left for DTE, use the default value let (data, dte) = if remaining_bits(data) > 0 { map(take_bits::<_, u8, _, _>(1u8), Into::into)(data)? @@ -117,7 +119,7 @@ mod tests { fn test_type5_truncated() { let bytestream = b"5341U9`00000uCGCKL0u=@T4000000000000001?<@<47u;b004Sm51DQ0C@"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let message = StaticAndVoyageRelatedData::parse(&bitstream).unwrap(); + let message = StaticAndVoyageRelatedData::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.message_type, 5); assert_eq!(message.repeat_indicator, 0); assert_eq!(message.mmsi, 205546790); @@ -141,7 +143,7 @@ mod tests { */ let bytestream = b"53`soB8000010KSOW<0P4eDp4l6000000000000U0p<24t@P05H3S833CDP000000000000"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let message = StaticAndVoyageRelatedData::parse(&bitstream).unwrap(); + let message = StaticAndVoyageRelatedData::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.message_type, 5); assert_eq!(message.repeat_indicator, 0); assert_eq!(message.mmsi, 244250440); diff --git a/src/messages/static_data_report.rs b/src/messages/static_data_report.rs index 6dc19c9..06f3def 100644 --- a/src/messages/static_data_report.rs +++ b/src/messages/static_data_report.rs @@ -3,6 +3,7 @@ use super::parsers::*; use super::types::*; 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; @@ -20,7 +21,7 @@ impl<'a> AisMessageType<'a> for StaticDataReport { "Static Data Report" } - fn parse(data: &[u8]) -> Result { + fn parse(data: &'a [u8]) -> Result { let (_, report) = parse_message(data)?; Ok(report) } @@ -33,18 +34,18 @@ pub enum MessagePart { /// Part A contains just the vessel name PartA { /// Name of the vessel in the report - vessel_name: String, + vessel_name: AsciiString, }, /// Part B is further split into two parts, depending on whether /// the broadcasting entity is an auxiliary craft, or of the main /// ship PartB { ship_type: Option, - vendor_id: String, - model_serial: String, + vendor_id: AsciiString, + model_serial: AsciiString, unit_model_code: u8, serial_number: u32, - callsign: String, + callsign: AsciiString, dimension_to_bow: u16, dimension_to_stern: u16, dimension_to_port: u16, @@ -61,7 +62,7 @@ fn parse_message_part(data: (&[u8], usize)) -> IResult<(&[u8], usize), MessagePa let (data, vessel_name) = parse_6bit_ascii(data, 120)?; // Senders occasionally skip sending the spare bits, so this is optional let (data, _spare) = - take_bits::<_, u8, _, _>(std::cmp::min(remaining_bits(data), 7))(data)?; + take_bits::<_, u8, _, _>(lib::std::cmp::min(remaining_bits(data), 7))(data)?; Ok((data, MessagePart::PartA { vessel_name })) } 1 => { @@ -127,7 +128,7 @@ mod tests { fn test_part_a_message() { let bytestream = b"H6:lEgQL4r1 { @@ -141,7 +142,7 @@ mod tests { fn test_part_b_main_vessel_message() { let bytestream = b"H3mr@L4NC=D62?P<7nmpl00@8220"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let message = StaticDataReport::parse(&bitstream).unwrap(); + let message = StaticDataReport::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.mmsi, 257855600); match message.message_part { MessagePart::PartB { @@ -166,7 +167,7 @@ mod tests { fn test_part_b_auxiliary_vessel_message() { let bytestream = b"H>cfmI4UFC@0DAN00000000H3110"; let bitstream = crate::messages::unarmor(bytestream, 0).unwrap(); - let message = StaticDataReport::parse(&bitstream).unwrap(); + let message = StaticDataReport::parse(bitstream.as_ref()).unwrap(); assert_eq!(message.mmsi, 985380196); match message.message_part { MessagePart::PartB { diff --git a/src/messages/utc_date_response.rs b/src/messages/utc_date_response.rs index 1f3c7ca..4ad9f44 100644 --- a/src/messages/utc_date_response.rs +++ b/src/messages/utc_date_response.rs @@ -92,7 +92,7 @@ mod tests { fn test_type11() { let bytestream = b";03sl8AvA;5AO7gnf@; +#[cfg(all(not(feature = "std"), not(feature = "alloc")))] +pub type AisRawData = lib::std::vec::Vec; + #[derive(PartialEq, Eq, Debug)] /// Represents the NMEA sentence type of an AIS message pub enum AisReportType { @@ -78,13 +85,13 @@ impl<'a> From<&'a [u8]> for TalkerId { } #[derive(Debug, PartialEq)] -pub enum AisFragments<'a> { - Complete(AisSentence<'a>), - Incomplete(AisSentence<'a>), +pub enum AisFragments { + Complete(AisSentence), + Incomplete(AisSentence), } -impl<'a> From> for Option> { - fn from(frag: AisFragments<'a>) -> Self { +impl From for Option { + fn from(frag: AisFragments) -> Self { match frag { AisFragments::Complete(sentence) => Some(sentence), AisFragments::Incomplete(_) => None, @@ -92,8 +99,8 @@ impl<'a> From> for Option> { } } -impl<'a> From> for Result> { - fn from(frag: AisFragments<'a>) -> Self { +impl From for Result { + fn from(frag: AisFragments) -> Self { match frag { AisFragments::Complete(sentence) => Ok(sentence), AisFragments::Incomplete(_) => Err("Incomplete message".into()), @@ -105,7 +112,7 @@ impl<'a> From> for Result> { pub struct AisParser { message_id: Option, fragment_number: u8, - data: Vec, + data: AisRawData, } impl AisParser { @@ -117,26 +124,26 @@ impl AisParser { /// Parses `line` as an NMEA sentence, checking the checksum and returning an /// an `AisSentence`. Note that several `AisSentence`s might be required to /// complete a message, if they are fragments - /// If `message` is `true`, the internal AIS message will also be parsed - /// If it is false, then internal AIS messages will be ignored. + /// If `decode` is `true`, the internal AIS message will also be parsed + /// If it is `false`, then internal AIS messages will be ignored. /// In both cases, AIS data will be passed along raw. - pub fn parse<'a>(&mut self, line: &'a [u8], decode: bool) -> Result> { + pub fn parse(&mut self, line: &[u8], decode: bool) -> Result { let (_, (data, mut ais_sentence, checksum)) = parse_nmea_sentence(line)?; Self::check_checksum(data, checksum)?; if ais_sentence.has_more() { if ais_sentence.fragment_number == 1 { self.message_id = ais_sentence.message_id; self.fragment_number = 0; - self.data = Vec::new(); + self.data = AisRawData::default(); } self.verify_and_extend_data(&ais_sentence)?; Ok(AisFragments::Incomplete(ais_sentence)) } else { if ais_sentence.is_fragment() { self.verify_and_extend_data(&ais_sentence)?; - let mut data = Vec::new(); - std::mem::swap(&mut data, &mut self.data); - ais_sentence.data = data.into(); + let mut data = AisRawData::default(); + lib::std::mem::swap(&mut data, &mut self.data); + ais_sentence.data = data; } if decode { let unarmored = @@ -155,7 +162,12 @@ impl AisParser { return Err("Fragment numbers out of sequence".into()); } self.fragment_number = ais_sentence.fragment_number; + #[cfg(any(feature = "std", feature = "alloc"))] self.data.extend_from_slice(&ais_sentence.data); + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + self.data + .extend_from_slice(&ais_sentence.data) + .map_err(|_| Error::from("Vec is full on extend_from_slice"))?; Ok(()) } @@ -175,20 +187,20 @@ impl AisParser { #[derive(Debug, PartialEq)] /// Represents an NMEA sentence parsed as AIS -pub struct AisSentence<'a> { +pub struct AisSentence { pub talker_id: TalkerId, pub report_type: AisReportType, pub num_fragments: u8, pub fragment_number: u8, pub message_id: Option, pub channel: Option, - pub data: Cow<'a, [u8]>, + pub data: AisRawData, pub fill_bit_count: u8, pub message_type: u8, pub message: Option, } -impl<'a> AisSentence<'a> { +impl AisSentence { /// Returns whether there are more fragments to come pub fn has_more(&self) -> bool { self.fragment_number < self.num_fragments @@ -202,12 +214,12 @@ impl<'a> AisSentence<'a> { /// Converts bytes representing an ASCII number to a string slice fn parse_numeric_string(data: &[u8]) -> IResult<&[u8], &str> { - map_res(digit1, std::str::from_utf8)(data) + map_res(digit1, lib::std::str::from_utf8)(data) } /// Converts bytes representing an ASCII number to a u8 fn parse_u8_digit(data: &[u8]) -> IResult<&[u8], u8> { - map_res(parse_numeric_string, std::str::FromStr::from_str)(data) + map_res(parse_numeric_string, lib::std::str::FromStr::from_str)(data) } /// Named parser for the AIS portion of an NMEA sentence @@ -228,6 +240,15 @@ fn parse_ais_sentence(data: &[u8]) -> IResult<&[u8], AisSentence> { let (data, _) = tag(",")(data)?; let (data, fill_bit_count) = verify(parse_u8_digit, |val| *val < 6)(data)?; let (_, message_type) = messages::message_type(ais_data)?; + #[cfg(any(feature = "std", feature = "alloc"))] + let ais_data_owned = ais_data.into(); + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + let ais_data_owned = ais_data.try_into().map_err(|_| { + nom::Err::Failure(nom::error::Error::new( + ais_data, + nom::error::ErrorKind::TooLarge, + )) + })?; Ok(( data, AisSentence { @@ -237,7 +258,7 @@ fn parse_ais_sentence(data: &[u8]) -> IResult<&[u8], AisSentence> { fragment_number, message_id, channel, - data: ais_data.into(), + data: ais_data_owned, fill_bit_count, message_type, message: None, @@ -284,7 +305,9 @@ mod tests { fragment_number: 1, message_id: None, channel: Some('A'), - data: Cow::Borrowed(&GOOD_CHECKSUM[AIS_START_IDX..AIS_END_IDX]), + data: GOOD_CHECKSUM[AIS_START_IDX..AIS_END_IDX] + .try_into() + .unwrap(), fill_bit_count: 0, message_type: 17, message: None, @@ -310,7 +333,12 @@ mod tests { fragment_number: 1, message_id: None, channel: Some('A'), - data: Cow::Borrowed(&GOOD_CHECKSUM[AIS_START_IDX..AIS_END_IDX]), + #[cfg(any(feature = "std", feature = "alloc"))] + data: GOOD_CHECKSUM[AIS_START_IDX..AIS_END_IDX].into(), + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + data: GOOD_CHECKSUM[AIS_START_IDX..AIS_END_IDX] + .try_into() + .unwrap(), fill_bit_count: 0, message_type: 17, message: None, @@ -332,7 +360,12 @@ mod tests { fragment_number: 1, message_id: None, channel: Some('A'), - data: Cow::Borrowed(&GOOD_CHECKSUM[AIS_START_IDX..AIS_END_IDX]), + #[cfg(any(feature = "std", feature = "alloc"))] + data: GOOD_CHECKSUM[AIS_START_IDX..AIS_END_IDX].into(), + #[cfg(all(not(feature = "std"), not(feature = "alloc")))] + data: GOOD_CHECKSUM[AIS_START_IDX..AIS_END_IDX] + .try_into() + .unwrap(), fill_bit_count: 0, message_type: 17, message: None,