bacnet-encoding 0.14.0

BACnet ASN.1/BER encoding, APDU and NPDU codecs per ASHRAE 135-2020
Documentation

bacnet-encoding

BACnet tag, primitive, APDU and NPDU codecs for the Rusty BACnet workspace. Encoding a value produces bytes; it does not send a message or establish that a peer accepted it.

Quickstart

The tag-sink API below is new in 0.14.0:

[dependencies]
bacnet-encoding = "0.14"

Write a Real tag header into caller-owned storage, then verify that a header which cannot fit leaves the existing bytes intact. This writes headers only; the four Real value bytes announced by the first header are not appended.

use bacnet_encoding::sink::SinkFull;
use bacnet_encoding::tags::{encode_opening_tag_to_sink, encode_tag_to_sink, TagClass};

fn main() -> Result<(), SinkFull> {
    let mut storage = [0xA5; 2];
    let mut tail = &mut storage[..];
    assert_eq!(encode_tag_to_sink(&mut tail, 4, TagClass::Application, 4)?, 1);
    let refused = encode_opening_tag_to_sink(&mut tail, 15).unwrap_err();
    assert_eq!((refused.needed, refused.available), (2, 1));
    assert_eq!(tail.len(), 1);
    assert_eq!(storage, [0x44, 0xA5]);
    Ok(())
}

The tag-sink functions append one complete header or return SinkFull without changing the sink. They do not validate every tag number or datatype-specific length, and their atomic write guarantee does not span several encoder calls. A variable-length value, such as a CharacterString with its header, goes out through EncodeSink::write_parts, all of it or none. See the sink design.

Features and allocation

The crate has three profiles. With neither std nor alloc it builds without an allocator (#1614), for firmware with no heap.

Profile Features What it has
Hosted std (default) Everything; enables alloc and the dependencies' standard-library support. Adds tracing for reserved NPDU control bits.
no_std with a heap alloc Everything but the tracing. Firmware must supply a global allocator.
No allocator neither Tag decoding and the _to_sink encoders (tags, scalars, timestamps, strings, octet and bit strings), the primitive decoders and the borrowed views (primitives::CharacterStringRef, primitives::ApplicationValueRef), the tagged-field readers in constructed::tagged, and apdu::header, which reads and writes the header of an Unconfirmed-Request, an unsegmented Confirmed-Request or ComplexACK and an Error, and a whole Reject or Abort. The owned APDU and NPDU, segmentation, the other constructed codecs and every BytesMut encoder need alloc.
heapless Adds sink::EncodeSink for heapless::Vec<u8, N>, in any profile.

EncodeSink also supports caller slices, and with alloc Vec<u8> and bytes::BytesMut. Only the alloc profile has bytes in its graph.

For an alloc-only dependency build, replace the dependency block with:

[dependencies]
bacnet-encoding = { path = "../rusty-bacnet/crates/bacnet-encoding", default-features = false, features = ["alloc"] }

For a build without an allocator, leave out features = ["alloc"]: the quickstart runs unchanged with any of the three blocks on a host. With alloc, firmware also needs a global allocator and a compatible bytes configuration: on targets without atomic compare-and-swap, such as ARMv6-M, enable bytes's extra-platforms feature and select a portable-atomic backend in the firmware. Without alloc there is no bytes, so no backend to choose. CI builds each profile for ARMv6-M, ARMv7E-M and RISC-V (scripts/ci/check-no-std.sh) but doesn't run it there. Cargo features are additive, so check whether another dependency enables alloc or std.

API documentation

This README is included in the crate's Rust documentation. The docs.rs profile explicitly enables std and heapless, disables implicit defaults, and renders only the default x86_64-unknown-linux-gnu target. That makes the optional sink adapter visible through its actual feature gate; it does not qualify a bare-metal target.