aivdm-cli 0.2.1

Command-line decoder for AIS (ITU-R M.1371) NMEA sentences, built on aivdm
aivdm-cli-0.2.1 is not a library.

aivdm

CI crates.io docs.rs license: MIT no_std

A no_std, allocation-free parser and encoder for AIS (Automatic Identification System) messages, per ITU-R M.1371, as carried over NMEA 0183 !AIVDM/!AIVDO sentences.

  • All 27 message types, decode and encode, verified against a corpus of real, independently-decoded AIS captures (not just synthetic round trips).
  • no_std, no alloc: works on bare-metal targets with no allocator. Multi-fragment sentence reassembly uses a fixed-capacity, caller-sized buffer instead of a growable one.
  • Zero mandatory dependencies. The bit-level codec is hand-rolled: AIS's irregular field widths (6, 12, 22, 27, 28, 30, 38 bits, ...) and spec-fixed sentinel values don't map cleanly onto byte-aligned derive macros.
  • #![forbid(unsafe_code)]. AIS messages are small (well under 1100 bits); there's no meaningful performance case for unsafe here.
  • A C FFI (aivdm-ffi) for calling the decoder from C or any language with a C FFI, with a CI-checked header and a linked, running smoke test.

Crates

Crate Description
aivdm The no_std, no-alloc parser/encoder library.
aivdm-cli A small command-line decoder (installs as aivdm).
aivdm-ffi Decode-only C FFI bindings.

Quick start

let msg = aivdm::decode_line("!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C")?;

let aivdm::AisMessage::PositionReportClassA(report) = msg else {
    panic!("expected a position report");
};

println!("mmsi={} lat={:?} lon={:?}",
    report.mmsi,
    report.latitude.as_degrees(),
    report.longitude.as_degrees(),
);

decode_line handles single-fragment sentences, which cover the large majority of AIS traffic. For multi-part messages, parse each line with Sentence::parse and feed the fragments through a FragmentAssembler (a fixed-capacity buffer sized by you, so it works without an allocator), then decode the reassembled payload with decode_payload.

Encoding mirrors decoding:

let mut buf = [0u8; 64];
let (armored, fill_bits) = aivdm::encode_payload(&msg, &mut buf)?;

Add it to a project with:

cargo add aivdm

From C

#include "aivdm.h"

AivdmError err;
AivdmMessage *msg = aivdm_decode_line(line, &err);
if (msg != NULL) {
    double lat, lon;
    if (aivdm_message_position(msg, &lat, &lon)) {
        printf("mmsi=%u lat=%f lon=%f\n", aivdm_message_mmsi(msg), lat, lon);
    }
    aivdm_message_free(msg);
}

See crates/aivdm-ffi for the full API and crates/aivdm-ffi/tests/smoke.c for a complete, working example. cargo build -p aivdm-ffi produces libaivdm_ffi.{a,so} alongside the checked-in crates/aivdm-ffi/include/aivdm.h; consumers who want a polished, pkg-config-discoverable libaivdm can instead use cargo-c (cargo capi build).

Message type coverage

All 27 ITU-R M.1371 message types are implemented, decode and encode:

Types What
1, 2, 3 Position Report Class A
4, 11 Base Station Report / UTC and Date Response
5 Static and Voyage Related Data
6, 8, 25, 26 Binary Addressed/Broadcast Messages
7, 13 Binary / Safety Related Acknowledge
9 Standard SAR Aircraft Position Report
10 UTC and Date Inquiry
12, 14 Addressed / Broadcast Safety Related Message
15 Interrogation
16 Assignment Mode Command
17 DGNSS Broadcast Binary Message
18, 19 Standard / Extended Class B Position Report
20 Data Link Management Message
21 Aid-to-Navigation Report
22 Channel Management
23 Group Assignment Command
24 Static Data Report (Class B, Parts A/B)
27 Long Range AIS Broadcast

Position reports, static/voyage data, base station reports, and the Class B variants were checked against real captured sentences with hand-verified field values. The rarer control/management types were additionally cross-referenced against libais's well-tested decoder and its real-world test corpus — see crates/aivdm/tests/real_world_corpus.rs.

Building

This project is built and checked with Nix:

nix develop        # dev shell: toolchain, rust-analyzer, cbindgen, cargo-c
nix flake check -L # fmt, clippy (pedantic), tests, no_std/no_alloc
                    # cross-compile verification, FFI header-diff, FFI smoke test
nix build           # aivdm-cli (default package)
nix build .#aivdm-ffi

CI runs the identical nix flake check -L, so there is one source of truth for what "passing" means, locally and in GitHub Actions.

Without Nix, a standard cargo build / cargo test from the workspace root also works with a recent stable Rust toolchain (edition 2024, MSRV tracked in Cargo.toml).

License

MIT. See LICENSE.