bacnet-encoding 0.15.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](https://github.com/jscott3201/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 has been available since 0.14.0:

```toml
[dependencies]
bacnet-encoding = "0.15"
```

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.

```rust
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](https://github.com/jscott3201/rusty-bacnet/blob/dev/docs/encoding-sinks.md).

## 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:

```toml
[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.