1#![no_std]
21#![forbid(unsafe_code)]
22#![warn(missing_docs)]
23
24pub mod bits;
25pub mod error;
26pub mod message;
27pub mod nmea;
28pub mod string;
29
30pub use error::AisError;
31pub use message::AisMessage;
32pub use message::common::*;
33pub use nmea::{Channel, CompleteMessage, FragmentAssembler, Sentence};
34
35pub fn decode_line(line: &str) -> Result<AisMessage, AisError> {
48 let sentence = Sentence::parse(line)?;
49 if sentence.fragment_count != 1 {
50 return Err(AisError::IncompleteFragment);
51 }
52 decode_payload(sentence.payload, sentence.fill_bits)
53}
54
55pub fn decode_payload(armored: &[u8], fill_bits: u8) -> Result<AisMessage, AisError> {
61 let mut reader = bits::BitReader::new(armored, fill_bits);
62 AisMessage::decode(&mut reader).map_err(AisError::from)
63}
64
65pub fn encode_payload<'a>(
73 message: &AisMessage,
74 buf: &'a mut [u8],
75) -> Result<(&'a [u8], u8), AisError> {
76 let mut writer = bits::BitWriter::new(buf);
77 message.encode(&mut writer)?;
78 Ok(writer.finish()?)
79}
80
81const MAX_SINGLE_SENTENCE_PAYLOAD_BYTES: usize = 128;
86
87pub fn encode_line<'a>(
107 message: &AisMessage,
108 channel: Channel,
109 is_own_ship: bool,
110 buf: &'a mut [u8],
111) -> Result<&'a [u8], AisError> {
112 let mut payload_buf = [0u8; MAX_SINGLE_SENTENCE_PAYLOAD_BYTES];
113 let (payload, fill_bits) = encode_payload(message, &mut payload_buf)?;
114
115 let sentence = Sentence {
116 is_own_ship,
117 fragment_count: 1,
118 fragment_number: 1,
119 seq_id: None,
120 channel,
121 payload,
122 fill_bits,
123 };
124 Ok(sentence.format(buf)?)
125}
126
127#[derive(Debug)]
135pub struct LineDecoder<const N: usize> {
136 assembler: FragmentAssembler<N>,
137}
138
139impl<const N: usize> Default for LineDecoder<N> {
140 fn default() -> Self {
141 Self::new()
142 }
143}
144
145impl<const N: usize> LineDecoder<N> {
146 #[must_use]
148 pub const fn new() -> Self {
149 Self {
150 assembler: FragmentAssembler::new(),
151 }
152 }
153
154 pub fn feed(&mut self, line: &str) -> Result<Option<AisMessage>, AisError> {
169 let sentence = Sentence::parse(line)?;
170 if sentence.fragment_count == 1 {
171 return decode_payload(sentence.payload, sentence.fill_bits).map(Some);
172 }
173
174 match self.assembler.push(&sentence) {
175 Ok(Some(complete)) => decode_payload(complete.armored, complete.fill_bits).map(Some),
176 Ok(None) => Ok(None),
177 Err(e) => {
178 self.assembler.reset();
179 Err(AisError::from(e))
180 }
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn decodes_a_real_position_report() {
191 let msg = decode_line("!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C").unwrap();
192 assert_eq!(msg.message_type(), 1);
193 let AisMessage::PositionReportClassA(p) = msg else {
194 panic!("expected PositionReportClassA");
195 };
196 assert_eq!(p.mmsi.raw(), 366_053_209);
197 assert_eq!(
198 p.navigation_status,
199 NavigationStatus::RestrictedManoeuvrability
200 );
201 assert!((p.longitude.as_degrees().unwrap() - (-122.341_618_333)).abs() < 1e-6);
202 assert!((p.latitude.as_degrees().unwrap() - 37.802_118_333).abs() < 1e-6);
203 }
204
205 #[test]
206 fn common_accessors_work_directly_on_ais_message() {
207 let msg = decode_line("!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C").unwrap();
208 assert_eq!(msg.mmsi().raw(), 366_053_209);
209 assert_eq!(
210 msg.navigation_status(),
211 Some(NavigationStatus::RestrictedManoeuvrability)
212 );
213 let position = msg.position().unwrap();
214 assert!((position.latitude - 37.802_118_333).abs() < 1e-6);
215 assert!((position.longitude - (-122.341_618_333)).abs() < 1e-6);
216 }
217
218 #[test]
219 fn multi_fragment_sentence_reports_incomplete() {
220 let err = decode_line("!AIVDM,2,1,7,B,15M67FC000,0*6C").unwrap_err();
221 assert_eq!(err, AisError::IncompleteFragment);
222 }
223
224 #[test]
225 fn encode_round_trips_a_real_position_report() {
226 let original_payload = b"15M67FC000G?ufbE`FepT@3n00Sa";
227 let msg = decode_payload(original_payload, 0).unwrap();
228
229 let mut buf = [0u8; 32];
230 let (armored, fill_bits) = encode_payload(&msg, &mut buf).unwrap();
231 assert_eq!(fill_bits, 0);
232 assert_eq!(armored, original_payload);
233
234 let re_decoded = decode_payload(armored, fill_bits).unwrap();
235 assert_eq!(re_decoded, msg);
236 }
237
238 #[test]
239 fn encode_line_round_trips_a_real_position_report() {
240 let line = "!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C";
241 let msg = decode_line(line).unwrap();
242
243 let mut buf = [0u8; 64];
244 let encoded = encode_line(&msg, Channel::B, false, &mut buf).unwrap();
245 assert_eq!(encoded, line.as_bytes());
246
247 let re_decoded = decode_line(core::str::from_utf8(encoded).unwrap()).unwrap();
248 assert_eq!(re_decoded, msg);
249 }
250
251 #[test]
252 fn encode_line_emits_aivdo_for_own_ship() {
253 let line = "!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C";
254 let msg = decode_line(line).unwrap();
255
256 let mut buf = [0u8; 64];
257 let encoded = encode_line(&msg, Channel::A, true, &mut buf).unwrap();
258 assert!(encoded.starts_with(b"!AIVDO,"));
259
260 let reparsed = Sentence::parse(core::str::from_utf8(encoded).unwrap()).unwrap();
261 assert!(reparsed.is_own_ship);
262 assert_eq!(reparsed.channel, Channel::A);
263 }
264
265 #[test]
266 fn line_decoder_decodes_single_fragment_immediately() {
267 let mut decoder = LineDecoder::<64>::new();
268 let msg = decoder
269 .feed("!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C")
270 .unwrap()
271 .unwrap();
272 assert_eq!(msg.mmsi().raw(), 366_053_209);
273 }
274
275 #[test]
276 fn line_decoder_reassembles_multi_fragment_sentences() {
277 let mut decoder = LineDecoder::<64>::new();
278 assert_eq!(
279 decoder.feed("!AIVDM,2,1,7,B,15M67FC000,0*6C").unwrap(),
280 None
281 );
282 let msg = decoder
283 .feed("!AIVDM,2,2,7,B,0G?ufbE`FepT@3n00Sa,0*26")
284 .unwrap()
285 .unwrap();
286 assert_eq!(msg.mmsi().raw(), 366_053_209);
287 }
288
289 #[test]
290 fn line_decoder_resyncs_after_an_invalid_fragment() {
291 let mut decoder = LineDecoder::<64>::new();
292 let err = decoder
294 .feed("!AIVDM,2,2,7,B,0G?ufbE`FepT@3n00Sa,0*26")
295 .unwrap_err();
296 assert_eq!(
297 err,
298 AisError::Fragment(crate::error::FragmentError::OutOfOrder)
299 );
300
301 assert_eq!(
303 decoder.feed("!AIVDM,2,1,7,B,15M67FC000,0*6C").unwrap(),
304 None
305 );
306 let msg = decoder
307 .feed("!AIVDM,2,2,7,B,0G?ufbE`FepT@3n00Sa,0*26")
308 .unwrap()
309 .unwrap();
310 assert_eq!(msg.mmsi().raw(), 366_053_209);
311 }
312}