Crate vita49

Crate vita49 

Source
Expand description

§VITA 49

A crate for parsing and creating packets compatible with the ANSI/VITA-49.2-2017 standard.

The ANSI/VITA 49.2 standard “defines a signal/spectrum protocol that expresses spectrum observation, spectrum operations, and capabilities of RF devices.”

In general, it’s difficult to write software for VITA 49 as software defined radios (SDRs) often choose different “flavors” of VITA 49.

This crate is:

  • Flexible: No need to recompile your program for different flavors of VITA 49.

  • Fast: Parsing is quick even while supporting various packet types.

  • Easy: Simply pass in some bytes and this crate will give you a data structure you can query, mutate, and write.

Crates.io Docs.rs Build Status License REUSE status

§Install

Add vita49 to your project with:

cargo add vita49

§Examples

Here’s some code to ingest VRT packets from a UDP socket. If it’s a signal data packet, it’ll just print the length and stream ID. If it’s a context packet, it’ll print all the fields that are present.

use std::net::UdpSocket;
use vita49::prelude::*;

fn main() -> Result<(), std::io::Error> {
    // Bind to a UDP socket
    let socket = UdpSocket::bind("0.0.0.0:4991")?;
    let mut buf = [0; 40960];

    println!("Entering receive loop...");
    loop {
        // Read in data from the socket
        let (bytes_read, _src) = socket.recv_from(&mut buf)?;

        // Try to parse it as a VRT packet
        let packet = Vrt::try_from(&buf[..bytes_read])?;

        // Do different things depending on the type of packet
        match packet.header().packet_type() {
            // If it's a signal data packet, just print the payload length
            PacketType::SignalData => {
                println!(
                    "Got signal data packet with stream ID 0x{:X} and a payload of length {}",
                    &packet.stream_id().unwrap(),
                    &packet.payload().signal_data().unwrap().payload_size_bytes()
                );
            }
            // If it's a context packet, print the fields (using the pre-
            // implemented Display trait)
            PacketType::Context => {
                println!(
                    "Got context packet:\n{}",
                    &packet.payload().context().unwrap()
                );
            }
            PacketType::Command => {
                println!(
                    "Got command packet:\n{}",
                    &packet.payload().command().unwrap()
                );
            }
            // Other packet types are not covered in this example
            _ => unimplemented!(),
        }
    }
}

See udp_recv.rs for the full example.

Here’s another example of generating and sending VRT packets:

use std::net::UdpSocket;
use vita49::prelude::*;

fn main() -> Result<(), std::io::Error> {
    // Bind to a UDP socket
    let socket = UdpSocket::bind("0.0.0.0:0")?;

    // Create a context packet with RF freq set to 100 MHz and
    // bandwidth set to 8 MHz.
    let mut packet = Vrt::new_context_packet();
    packet.set_stream_id(Some(0xDEADBEEF));
    let context = packet.payload_mut().context_mut().unwrap();
    context.set_rf_ref_freq_hz(Some(100e6));
    context.set_bandwidth_hz(Some(8e6));
    packet.update_packet_size();

    // Send the packet
    socket.send_to(&packet.to_bytes()?, "127.0.0.1:4991")?;

    // Create a signal data packet with some dummy data.
    let mut sig_packet = Vrt::new_signal_data_packet();
    sig_packet.set_stream_id(Some(0xDEADBEEF));
    sig_packet
        .set_signal_payload(&vec![1, 2, 3, 4, 5, 6, 7, 8])
        .unwrap();

    // Send the packet
    socket.send_to(&sig_packet.to_bytes()?, "127.0.0.1:4991")?;

    Ok(())
}

See udp_send.rs for the full example.

You can actually run these two examples locally to see the output. In one terminal window, run:

cargo run --example udp_recv

Then, in another window, run:

cargo run --example udp_send

On the receive end, you should see something like:

Entering receive loop...
Got context packet:
CIF0:
  Context field change indicator: false
  Reference point identifier: false
  Bandwidth: true
  IF reference frequency: false
  RF reference frequency: true
  RF reference frequency offset: false
  IF band offset: false
  Reference level: false
  Gain: false
  Over-range count: false
  Sample rate: false
  Timestamp adjustment: false
  Timestamp calibration time: false
  Temperature: false
  Device identifier: false
  State/event indicators: false
  Signal data format: false
  Formatted GPS: false
  Formatted INS: false
  ECEF ephemeris: false
  Relative ephemeris: false
  Ephemeris ref ID: false
  GPS ASCII: false
  Context association lists: false
  CIF7: false
  CIF3: false
  CIF2: false
  CIF1: false
Bandwidth: 8000000 Hz
RF reference frequency: 100000000 Hz

Got signal data packet with stream ID 0xDEADBEEF and a payload of length 8

§Command & Control

VITA 49.2 introduces the ability to perform command and control (C2) operations using VITA packets. For an example of both sides of a C2 flow, see the NATS control example programs.

§C++ Interoperability

For an example of how to use this crate from a C++ app, see cxx_demo/README.md.

§Python Interoperability

For an example of how to use this crate from a Python app, see pyo3_demo/README.md.

§Crate features

By default, this crate does not enable any of its optional features, leaving them as “opt-in” by the user.

§cif7

This feature enables CIF7 support.

To use this feature, enable it in your Cargo.toml:

vita49 = { version = "0.1.0", features = ["cif7"] }

CIF7, also known as “field attributes”, add an ability to provide descriptive statistics of various fields along with their current value. This does, however, add some parsing overhead as every field then includes up to 31 additional, optional fields

With benchmark tests (via cargo bench), we can see as of the time of this writing that enabling CIF7 support yields a 20% performance reduction in signal data packets and a 63% performance reduction in context packets.

Since the feature isn’t used widely and impacts performance, it’s left disabled by default.

§serde

This feature enables serde support.

To use this feature, enable it in your Cargo.toml:

vita49 = { version = "0.1.0", features = ["serde"] }

With this feature enabled, you can serialize/deserialize structures provided by this crate with serde. For example, to print a VRT packet as JSON:

use vita49::prelude::*;
#[cfg(feature = "serde")]
{
    let mut packet = Vrt::new_context_packet();
    let context = packet.payload_mut().context_mut().unwrap();
    context.set_bandwidth_hz(Some(8e6));
    packet.update_packet_size();
    println!("{}", serde_json::to_string_pretty(&packet).unwrap())
}

This yields:

{
  "header": {
    "hword_1": 17005,
    "packet_size": 29
  },
  "stream_id": 1,
  "class_id": null,
  "integer_timestamp": 60045,
  "fractional_timestamp": 411360110,
  "payload": {
    "Context": {
      "cif0": 673316866,
      "cif1": 1032,
  "etc": "...",

This repo has some test VRT packets stored as JSON strings for visibility. An example program is provided to convert these to raw VRT files under vita49/examples/json2vrt.rs. You can run this program via cargo:

% cargo run --features=serde --example json2vrt vita49/tests/spectral_data_packet.json5
   Compiling vita49 v0.1.0 (vita49/vita49)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.71s
     Running `target/debug/examples/json2vrt vita49/tests/spectral_data_packet.json5`
Wrote VRT data to vita49/tests/spectral_data_packet.vrt

§deku-log

Enables the deku crate’s logging feature.

§TODO

According to Section 1.3 of ANSI/VITA-49.2-2017:

V49.2 provides millions of options for data types and packet structures that are likely impossible to implement as one application program interface (API) without culling down to required attributes.

While we’d love to cover every possible combination VITA 49 can offer, it has to be a community effort to get there. We’ve implemented full support for things that we use and things that we figure would be most likely to be used by others, but we don’t have 100% coverage.

If there’s a field you’d like to use and you don’t feel able to add support for it yourself (and hopefully contribute your code back!), feel free to file an issue and we’ll know what to prioritize.

There are basically three states of “doneness” for various fields:

  1. Full support
  2. Basic support
  3. No support

Fully supported fields have getters and setters that deliver a clean interface to the user (e.g. fixed point fields are translated to primitive f64 or a multi-word data structure is filled out in a meaningful struct).

Basic support means the field is there and can be parsed/set, but it’d be better to implement a fully-featured struct. Using these fields means you might need to do some bit masking/shifting yourself. These fields are marked with a comment: // TODO: add full support.

No support means the crate will panic!() if the field is encountered. This is usually because the field can be variable length and, until support for the field is added, we can’t guarantee the packet will be parsed correctly. These fields are marked with a macro: todo_cif_field!(). The getters/setters associated with these fields are marked with a comment: // TODO: add basic support.

§Debugging

If this crate is unable to parse some VITA 49 data you’re working with, there are two possible causes:

  1. The VITA 49 packet is not compliant with the standard.
  2. There is a bug in the crate.

Either or both causes are very possible!

The [Deku] crate used for binary parsing provides a helpful trace logging option that can be invaluable for troubleshooting these low-level issues.

To enable this logging:

  1. Enable the deku-log feature of this crate.
  2. Import the log crate and a compatible logging library.

For example, to log with env_logger, you may add to your Cargo.toml:

vita49 = { version = "*", features = ["deku-log"] }
log = "*"
env_logger = "*"

Then you’d call env_logger::init() or env_logger::try_init() prior to attempting to parse a packet.

Then, each field being parsed will print as it goes:

[TRACE vita49::vrt] Reading: Vrt.header
[TRACE vita49::packet_header] Reading: PacketHeader.hword_1
[TRACE deku::reader] read_bytes_const: requesting 2 bytes
[TRACE deku::reader] read_bytes_const: returning [41, 00]
[TRACE vita49::packet_header] Reading: PacketHeader.packet_size
[TRACE deku::reader] read_bytes_const: requesting 2 bytes
[TRACE deku::reader] read_bytes_const: returning [00, 07]
[TRACE vita49::vrt] Reading: Vrt.stream_id
[TRACE deku::reader] read_bytes_const: requesting 4 bytes
[TRACE deku::reader] read_bytes_const: returning [de, ad, be, ef]

From here, you can step through the packet you’re trying to parse and see if it’s setting something incorrectly or the crate is parsing something incorrectly.

If you think you’ve found a bug, please do report it! See CONTRIBUTING.md for more info on how to do that.

§Minimum Rust Version Policy

This crate’s minimum supported rustc version is 1.71.0.

The minimum supported rustc version may be increased in minor version updates of this crate. For example, if vita49 1.2.0 requires Rust 1.60.0, versions 1.2.X of vita49 will also require Rust 1.6.0 or newer. However, vita49 version 1.3.0 may require a newer minimum version of Rust.

§Crate Versioning

This project adheres to Semantic Versioning.

After initial project release (0.1.0), any changes that would break API compatibility will require a major version number bump.

§License

Licensed under either of

at your option.

§Contributing

See CONTRIBUTING.md for details.

Modules§

command_prelude
Standard imports for programs that interact with VITA 49.2 command/control/ack packets.
prelude
Standard imports for the most commonly used structures and traits in the vita49 crate.

Structs§

Ack
ACK data structure shared by validation and execution ACK packets.
AckResponse
ACK response field. Each warning or error field in an ACK packet uses one of these regardless of the underlying field’s data type. For example, even though bandwidth is a 64-bit field in VITA 49, the response field is always 32-bits that represent various things that might be wrong with the bandwidth.
Cancellation
Cancellation packet data structure. This is similar to a control packet, but does not include data fields for the set CIF fields. In other words, it only contains indicator fields for the fields you’d like to cancel.
Cif0
Base data structure for the CIF0 single-bit indicators.
Cif0AckFields
Structure for all cif0 data fields (not indicators)
Cif0Fields
Structure for all cif0 data fields (not indicators)
Cif1
Base data structure for the CIF1 single-bit indicators
Cif2
Base data structure for the CIF2 single-bit indicators
Cif3
Base data structure for the CIF3 single-bit indicators
Cif7
Base data structure for the CIF7 single-bit indicators.
Cif1AckFields
Structure for all cif1 data fields (not indicators)
Cif1Fields
Structure for all cif1 data fields (not indicators)
Cif2AckFields
Structure for all cif2 data fields (not indicators)
Cif2Fields
Structure for all cif2 data fields (not indicators)
Cif3AckFields
Structure for all cif3 data fields (not indicators)
Cif3Fields
Structure for all cif3 data fields (not indicators)
ClassIdentifier
Base class identifier data structure.
Command
Main command payload structure.
CommandIndicators
Command packet indicators.
Context
Context packet payload. Includes all CIFs and optional fields.
ContextAssociationLists
Base context association lists structure.
ContextIndicators
Context packet indicator fields.
Control
Data structure for control packets. Very similar to Context, but reversed. All the same fields are used, but processed by a controllee to set fields rather than report the current value.
ControlAckMode
Base CAM field data structure.
DeviceId
Base device ID data structure.
EcefEphemeris
Base ECEF ephemeris data structure.
FormattedGps
Base formatted GPS data structure.
Gain
Base gain data structure.
GpsAscii
Base ASCII GPS data structure.
PacketHeader
Base packet header data structure.
QueryAck
Query ACK data structure used to report current state back to some controller. Functionally, this packet is very similar to Context, but is produced on-demand, not in-line with signal data.
SignalData
Base signal data structure.
SignalDataIndicators
Signal data indicator fields.
Spectrum
Base spectrum field data structure.
Threshold
Base threshold data structure.
Trailer
Base trailer field data structure.
Vrt
The main VRT data structure that encapsulates all types of VRT packets.
WindowTimeDelta
Window time delta structure.

Enums§

AckLevel
ACK level indicating if the ACK is a warning or error.
ActionMode
Control action mode.
AveragingType
Type of averaging being performed.
CommandPayload
Command payload enumeration. Command payloads can take several different forms depending on various header and CAM fields. Basically, here’s the breakdown:
EmsOrganizationRelationship
Enum to describe the various EMS device relationships. See ANSI/VITA-49.2-2017 section 9.8.9 for details.
IdFormat
Identification format (128-bit UUID or 32-bit ID).
Indicators
Indicator field enumeration. The three indicator bits have different meaning depending on if the packet is a signal data, context, or command packet.
PacketType
The type of VRT packet being worked on.
Payload
Generic payload enumeration. The payload format will differ depending on the type of packet.
SpectrumType
Type of spectral data being presented.
TimestampMode
Timestamp mode
TimingControlMode
Timing control mode.
Tsf
TimeStamp-Fractional (TSF) field.
Tsi
TimeStamp-Integer (TSI) field.
VitaError
Generic vita49 crate error enumeration.
WindowTimeDeltaInterpretation
Interpretation options for the window time delta field.
WindowType
Window type enumeration.

Traits§

Cif0AckManipulators
Shared trait for manipulating CIF0 ACK fields.
Cif0Manipulators
Trait for common CIF0 manipulation methods. Used by Context and Command packets.
Cif1AckManipulators
Shared trait for manipulating CIF1 ACK fields.
Cif1Manipulators
Trait for common CIF1 manipulation methods. Used by Context and Command packets.
Cif2AckManipulators
Shared trait for manipulating CIF2 ACK fields.
Cif2Manipulators
Trait for common CIF2 manipulation methods. Used by Context and Command packets.
Cif3AckManipulators
Shared trait for manipulating CIF3 ACK fields.
Cif3Manipulators
Trait for common CIF3 manipulation methods. Used by Context and Command packets.