Repository navigation
Releases: otfabric/go-bacnet
Release list
go-bacnet v0.4.0
Date: unreleased
Previous release: v0.3.0
Summary
Review release. The client and the codecs were audited against the standard,
against four peer stacks and against Wireshark's dissector; the defects found
are fixed, the client reports invalid configuration instead of adjusting it,
remote errors carry their standard names, and logging is available through
log/slog. The bacnetctl command moved to its own repository,
otfabric/bacnetctl; this module is a
library only again. Requires Go 1.23+.
Tested against bacnet-stack 1.6.1, BACpypes3 0.0.110, BACnet4J 6.1.1,
Worldiety 3cb2aa80 and System.IO.BACnet 4.0.0 (206 peer scenarios), and
against Wireshark 4.2–4.6 for the request layouts.
Interop pin → bacnet-interop v0.10.0
(interop/bacnet-interop-pin.json, seven images by digest, two of them new:
sysiobacnet and bacnet-stack-router). This version needs that release:
five codec vectors of the fixture corpus were corrected together with the
encoders they were derived from, so internal/fixtures fails against the
v0.9.0 corpus.
This release changes behaviour and some exported identifiers. Read
"Changed" and "Removed" before upgrading. Still not validated against
physical devices (field validation).
Fixed
Client:
- Closing the client while a subscription request was in flight could
panic with "close of closed channel":Closeand the failing subscribe both
ended the half-made subscription. - A request overtaken by
Closecould fail with the transport's error
("use of closed network connection") instead ofErrClosed. - Invoke-ID allocation reserved nothing: two concurrent requests could be
given the same invoke ID when few were free, and more requests than the
configured maximum could be in flight. Allocation now reserves the ID. - Data race between a request being sent and
Close(the "sent" flag). - Receive loop spun at full speed while the socket kept failing (interface
down). It now backs off (5 ms doubling to 500 ms) and reports atransport
diagnostic. - ReadProperty of the Device wildcard (
device,4194303) failed with
ErrProtocolViolationbecause the reply names the real device instance. ReadRangeAllstopped after the first page whenever a page returned all
the items it asked for (the device clears MORE_ITEMS then), andMaxItems
counted decoded elements instead of records. Found by the new peer tests,
on all three peers that serve ReadRange.ReadPropertyMultipleBatchedsplit by object only, so one object with
many properties was never split; retried after Abort reason 5
(security-error) instead of 1 (buffer-overflow) and 11 (apdu-too-long); and
returned results per batch instead of per input specification.- A
Targetwith only an endpoint sent the request and then discarded the
answer as coming from the wrong source, ending in a timeout. The address of
a directly reachable device is now derived from the endpoint. - Broadcasts went to 255.255.255.255 only. Devices listen for the
broadcast address of their subnet, and many do not receive the limited
broadcast: of the four peer stacks, a broadcast Who-Is reached two. A
broadcast is now sent to the directed broadcast address of every local
subnet (or of theWithInterfaceinterface) and to 255.255.255.255.
The operating system sends 255.255.255.255 on one interface only, that of
the default route: on a host with several interfaces a device that hears
only this form was not reached on the others. On Linux and macOS the
limited broadcast is now sent on each interface in turn (found by the peer
tests in CI, whose runner has a Docker bridge next to its uplink).
Found by running the CLI against the peers; the peer tests had only used
unicast Who-Is. - A plain Error in answer to WritePropertyMultiple (System.IO.BACnet
sends one instead of a WritePropertyMultiple-Error) was reported as a
malformed frame. It is now an*ErrorResponsewith the device's class and
code. - An Error PDU naming another service than the request (BACnet4J answers
a refused SubscribeCOVPropertyMultiple with error-choice 127) was dropped,
so the caller got a timeout, or outcome-unknown for a write. Invoke ID and
source identify the transaction; the Error now completes the request. - Unsolicited confirmed requests were ignored, leaving the sender to
retry. They are answered with Reject (unrecognized-service), Abort
(segmentation-not-supported) for segmented ones, and Reject for a
notification that does not decode.
Codecs (each with a regression test; layouts marked ✓ were also checked
against Wireshark's dissector):
- NPDU Hop Count was written and read directly after DADR. It belongs
after SADR, which matters whenever both a destination and a source network
address are present, e.g. an I-Am from a routed device answering a global
Who-Is: such frames were misparsed. ✓ - Router-Busy-To-Network and Router-Available-To-Network were swapped
(npdu.NetMsgRouterBusyToNetworkis X'04',…Available…X'05'): a router
announcing congestion was treated as available again, and the reverse. ✓ - An empty list or array could not be read. A property whose value is an
empty list (Device_Address_Binding or Active_COV_Subscriptions of a device
without any, for example) arrives as an empty value (3E 3F), which
ReadProperty refused as malformed; in a ReadPropertyMultiple reply it
discarded every other result too. It now decodes as a constructed value
without elements. Reproduced on bacnet-stack, BACnet4J, BACpypes3 and
System.IO.BACnet; found by the fifth peer's test suite. - Audit services did not interoperate. The audit notification request was
encoded and expected as three loose fields instead of
notifications [0] SEQUENCE OF BACnetAuditNotification, so notifications
from bacnet-stack and BACpypes3 produced no events; AuditLogQuery lacked the
required query parameters and BACpypes3 could not decode it. Both are
rewritten to Clause 21: the client now decodes the notifications and log
records of two independent upstream encoders, and BACpypes3 decodes the
query field by field. ✓ - ReadRange ACK: a peer-controlled item count sized an allocation for
Log_Buffer records (up to hundreds of gigabytes requested from a 36-octet
datagram). The count is now bounded by the data present. - Error PDUs with a structured body (CreateObject, AddListElement,
RemoveListElement, ConfirmedPrivateTransfer, VT-Close,
SubscribeCOVPropertyMultiple) were reported asErrMalformed; they now
yield*ErrorResponsewith the device's class and code. - COV notifications carrying a priority (
BACnetPropertyValue.priority)
were rejected as malformed and dropped. - SubscribeCOVPropertyMultiple request ✓, GetEnrollmentSummary
priority filter ✓, COVNotificationMultiple time-of-change, HostNPort,
out-of-range / floating-limit notification parameters: tags did not
follow the standard's ASN.1. - The BACnet
Errorwas encoded with context tags (09 02 19 20) by
EncodeBACnetErrorand therefore by the ReadPropertyMultiple-ACK and
WritePropertyMultiple-Error encoders. It is two application ENUMERATEDs
(91 02 91 20). Only code that plays the server side (simulators, tests)
was affected; decoding always accepted the standard form and still accepts
the old one. - CalendarEntry date and weekNDay, Date_List, Daily/WeeklySchedule
could not decode real wire data. AppendTagsilently emitted an empty context tag for a typed value.- Life-safety enumerations:
LifeSafetyMode,LifeSafetyStateand
LockStatushad wrong values (see "Changed"). - Tag decoder: non-canonical application Booleans, out-of-range BitString
unused-bit counts and invalid extended tag numbers were accepted; a length
check could overflow on 32-bit platforms. - Identifiers, array indexes and counts wider than 32 bits were truncated
instead of rejected;EncodeWritePropertyaccepted priorities outside
1..16; several decoded values aliased the receive buffer. - GetEnrollmentSummary ACK entries without the optional notification
class, and You-Are with the optional MAC address or without a device
identifier, were rejected.
Added
client.WithLogger(*slog.Logger): leveled logging of client diagnostics
(OBSERVABILITY.md). The client stays silent without it.bacnet.ErrInvalidConfig;client.Newvalidates options.bacnet.ErrorClassName,ErrorCodeName,RejectReasonName,
AbortReasonNameand constants for the standard values; remote error
messages now readclass=object(1) code=unknown-object(31).bacnet.AnyDevice,WildcardInstance,ObjectIdentifier.Matches.DiscoveryOptions.Window/WhoHasOptions.Window:Discoverand
DiscoverObjectsreturn a nil error after the window.DeviceObservation.Target()and.DeviceID().ReadObjectListfalls back to reading the list element by element when the
device cannot return it in one reply.client.ErrNoRoute(wrapsErrUnsupported) fromResolveTarget;
Client.AllRoutes().Client.SubscribeCOVMultiple: a managed SubscribeCOVPropertyMultiple
subscription (renewed at half the lifetime, cancelled byClose) with
COVMultipleOptions.MaxNotificationDelay.SubscriptionEvent.Multiple
carries the whole COVNotificationMultiple, with its timestamp and each
value's time of change.SubscribeCOVPropertyMultipleRequest.Cancellation
encodes the cancellation (no issue-confirmed-notifications, no lifetime).
Before, a COVNotificationMultiple reached the caller only as per-object
notifications of a single-object s...
v0.3.0
Date: 2026-08-03
Previous release: v0.2.5
Tag: v0.3.0
Summary
Client-completeness release: confirmed-service policy registry, BVLC BDT/FDT
management, multi-route state, adaptive RPM/WPM/file/ReadRange helpers, optional
event dispatcher and device snapshot, Schedule/Calendar/HostNPort constructed
types, property-aware decoding, and expanded NotificationParameters. Pin moves
to bacnet-interop v0.9.0
@ 180006f. Requires Go 1.23+.
Still production-candidate until
field validation is complete.
Added
- ConfirmedServicePolicy / ConfirmedServiceCapabilities registry —
retransmit safety, outcome-unknown after send,OperationClassopt-in, and
success-response form separated from segmentation capabilities WithNetworkManagementEnabled— opt-in for BVLC Write-BDT
(ErrNetworkManagementDisabledwithout confirm)- Typed BVLC BDT/FDT codecs (
bvlc.BDTEntry/FDTEntry,IPv4Mask) and
client APIs:ReadBroadcastDistributionTable,
WriteBroadcastDistributionTable,ReadForeignDeviceTable,
DeleteForeignDeviceTableEntry(*BVLCOperationErroron non-zero Result) - Multi-route cache with transaction-stable next-hop selection; Busy /
Available / Reject update route state for later transactions only - Adaptive helpers:
ReadPropertyMultipleBatched,
WritePropertyMultipleBatched(Completed/Failed/Unknown),
ReadFileStream/ReadFileRecords/WriteFileStream,ReadRangeAll,
ReadObjectList/ReadPropertyList/WritePriority/RelinquishPriority - Optional
WithEventDispatcher(bounded workers, overflow policy, panic
recovery); confirmed EventNotifications still SimpleACK'd before enqueue - Optional
device.ReadSnapshotviaPropertyReader(client does not
importdevice) - Constructed types: DateRange / DateTime / TimeStamp, CalendarEntry,
WeeklySchedule / ExceptionSchedule, HostNPort DecodePropertyValuewithApplicationValuefallback; access / life-safety
enums; expanded object-type and property-identifier constants; ID name maps- NotificationParameters: change-of-life-safety, extended, complex-event-type
- NPDU network-message constants and codecs for Reject-Message-To-Network,
I-Could-Be-Router-To-Network, Network-Number-Is validation internal/faultytransport for fault-injection tests;bacnet.ErrBusy- Interop: BDT/FDT / Write-BDT / Delete-FDT scenarios
(interop/bvlc_mgmt_test.go) against bacnet-stack, BACpypes3, BACnet4J
Changed
- Pin →
bacnet-interopv0.9.0
(interop/bacnet-interop-pin.json) — digests from the published release - Typed confirmed helpers look up the policy registry for retransmission and
outcome-unknown wrapping (broader coverage than the prior hard-coded set) InvokeConfirmed/ConfirmedInvokeOptions: additiveRetransmitand
SideEffectingfields; default remains non-retransmitting; outcome-unknown
wraps only whenSideEffectingis true- Retransmit send failures emit diagnostics instead of being silently ignored
- Docs: CLIENT_SUPPORT / INTEROP / API / PLAN reflect closed client-completeness
backlog and BBMD peer limits (Write-BDT NAK on stack/BACpypes3; BACnet4J live)
Breaking (v0.x)
npdu.NetMsgRouterNetworkNumberIsrenamed tonpdu.NetMsgNetworkNumberIs
(same wire value0x13)
Evidence
Gates green on main @ 7fd28e5 (re-attach Tag SHA if the release commit
differs):
| Gate | Result | Link |
|---|---|---|
| Shared CI (lint, Go matrix, race, coverage, PR fuzz) | green | 30776971381 |
Pinned interop v0.9.0 |
green | 30776971164 (job Pinned release peers) |
| bacnet-interop main compat | green | same run (job bacnet-interop main compat) |
| Pin | bacnet-interop v0.9.0 @ 180006f |
interop/bacnet-interop-pin.json |
Live BBMD / FDT scenarios on the pinned digests:
| Scenario | Peers | Test / evidence |
|---|---|---|
| Read-BDT | bacnet-stack + BACpypes3 + BACnet4J | Test*ReadBDT |
| Read-FDT (after FD) | bacnet-stack + BACpypes3 + BACnet4J | Test*ReadFDT* |
| Write-BDT | BACnet4J OK; stack/BACpypes3 NAK asserted | Test*WriteBDT* |
| Delete-FDT | bacnet-stack + BACpypes3 + BACnet4J | Test*DeleteFDT* |
Prior application-service and FDR scenarios from v0.2.5 remain on the new
digests.
Notes / known skips
- Worldiety BBMD / native multi-homed BIP router unavailable at pin (routed
tests usebip-router; see bacnet-interopPEER_SUPPORT.md) - Audit / AuthRequest / VT remain codec/unit — no peer servers at pin
- Field validation still pending (FIELD_VALIDATION.md)
- Library coverage gate held at ≥90%
v0.2.5
Date: 2026-08-03
Previous release: v0.2.4
Summary
Interop pin and documentation update. No new client APIs. Pin moves to
bacnet-interop v0.8.0
@ 34f42dc (native router/BBMD audit; bacnet-stack peer-as-BBMD confirmed).
Requires Go 1.23+.
Changed
- Pin →
bacnet-interopv0.8.0
(interop/bacnet-interop-pin.json) — digests from the published release - Interop:
TestBacnetStackForeignDeviceWhoIsReadProperty(peer-as-BBMD / FDR) - Docs: lean client-support baseline; INTEROP BBMD matrix; CLIENT_SUPPORT
Register-Foreign-Device multi-peer evidence; PLAN client-completeness backlog
Evidence
| Gate | Result | Link |
|---|---|---|
| Shared CI (lint, Go matrix, race, coverage, PR fuzz) | green | 30773312858 |
Pinned interop v0.8.0 |
green | 30773312737 |
| Pin | bacnet-interop v0.8.0 @ 34f42dc |
interop/bacnet-interop-pin.json |
v0.2.4
Date: 2026-08-03
Previous release: v0.2.3
Tag: v0.2.4 @ daef989
Summary
Evidence and wire-correctness patch after
v0.2.3.
No new client APIs. Pin moves to
bacnet-interop v0.7.0
@ 224f51b. Requires Go 1.23+.
Container-interoperability validated; field validation
pending.
Fixed
- Who-Am-I / You-Are: application-tagged SEQUENCE (ASHRAE unnamed fields),
matching bacnet-stackwhoami/youare(was incorrect context tags) - LifeSafetyOperation
requestingSource: context-primitive CharacterString
(was constructed wrapper); matches BACnet4J + bacnet-stack - TextMessage:
messageClassCHOICE + context-primitivemessageText(was
numeric-only class + constructed text); enables BACnet4J / BACpypes3 receipt
Changed
- Pin →
bacnet-interopv0.7.0
(interop/bacnet-interop-pin.json) - Interop harness retains peer
{"event":"operation",…}JSONL after ready
(and tees to an evidence file for Docker Desktop re-exec)
Evidence
| Gate | Result | Link |
|---|---|---|
| Shared CI (lint, Go matrix, race, coverage, PR fuzz) | green | 30771576116 |
Pinned interop v0.7.0 |
green | 30771575928 |
| bacnet-interop main compat | green | same interop run |
| Pin | bacnet-interop v0.7.0 |
interop/bacnet-interop-pin.json |
| GitHub Release | published | v0.2.4 |
Live scenarios on the pinned digests:
| Scenario | Peers | Test / evidence |
|---|---|---|
| GetAlarmSummary (AV Out_Of_Range) | BACnet4J + bacnet-stack | TestGetAlarmSummaryOutOfRange |
| GetEnrollmentSummary (EE-1) | BACnet4J | TestBACnet4JGetEnrollmentSummary |
| SubscribeCOVPropertyMultiple / COVNotificationMultiple | — | codec/unit only |
Messaging semantic receipt (v6 operation JSONL) |
BACnet4J + BACpypes3 + bacnet-stack | TestMessagingSemanticReceipt |
| Who-Am-I / You-Are (v7) | bacnet-stack | TestBacnetStackWhoAmIYouAre |
| LifeSafetyOperation LSP-1 (v8) | BACnet4J + bacnet-stack | TestLifeSafetyOperation |
| Audit notification / AuditLogQuery / AuthRequest / VT | — | codec/unit only |
Prior File / Create-Delete / NC list live scenarios from v0.2.3 remain on the
new digests.
Notes / known skips
- Worldiety segmented WPM/RPM: continuation ServiceChoice deviation (see
bacnet-interopPEER_SUPPORT.md) - Open: native router/BBMD peer audit (bacnet-interop
BLOCKERS.md) - Library coverage gate held at ≥90%
v0.2.4
Date: 2026-08-03
Previous release: v0.2.3
Tag: pending (cut via Actions → Release → patch)
Summary
Evidence and wire-correctness patch after
v0.2.3.
No new client APIs. Pin moves to
bacnet-interop v0.7.0
@ 224f51b (Batches A–C adapters). Requires Go 1.23+.
Still production-candidate until the
real-device gate is complete.
Fixed
- Who-Am-I / You-Are: application-tagged SEQUENCE (ASHRAE unnamed fields),
matching bacnet-stackwhoami/youare(was incorrect context tags) - LifeSafetyOperation
requestingSource: context-primitive CharacterString
(was constructed wrapper); matches BACnet4J + bacnet-stack - TextMessage:
messageClassCHOICE + context-primitivemessageText(was
numeric-only class + constructed text); enables BACnet4J / BACpypes3 receipt
Changed
- Pin →
bacnet-interopv0.7.0
(interop/bacnet-interop-pin.json) - Interop harness retains peer
{"event":"operation",…}JSONL after ready
(and tees to an evidence file for Docker Desktop re-exec)
Evidence
Fill CI links after the Release workflow runs on the tag SHA:
| Gate | Result | Link |
|---|---|---|
| Shared CI (lint, Go matrix, race, coverage, PR fuzz) | pending | — |
Pinned interop v0.7.0 |
pending | — |
| bacnet-interop main compat | pending | — |
| Pin | bacnet-interop v0.7.0 |
interop/bacnet-interop-pin.json |
| GitHub Release | pending | — |
Live scenarios on the pinned digests (Batches A–C):
| Scenario | Peers | Test / evidence |
|---|---|---|
| GetAlarmSummary (AV Out_Of_Range) | BACnet4J + bacnet-stack | live-multi-peer TestGetAlarmSummaryOutOfRange |
| GetEnrollmentSummary (EE-1) | BACnet4J | live-single-peer TestBACnet4JGetEnrollmentSummary |
| SubscribeCOVPropertyMultiple / COVNotificationMultiple | — | codec-only (all peers unsupported-upstream) |
Messaging semantic receipt (v6 operation JSONL) |
BACnet4J + BACpypes3 + bacnet-stack | TestMessagingSemanticReceipt (per-service EVIDENCE matrix) |
| Who-Am-I / You-Are (v7) | bacnet-stack | live-single-peer TestBacnetStackWhoAmIYouAre |
| LifeSafetyOperation LSP-1 (v8) | BACnet4J + bacnet-stack | live-multi-peer TestLifeSafetyOperation |
| Audit notification / AuditLogQuery / AuthRequest / VT | — | codec-only (B7e2–e4 / B7g) |
Prior live-multi-peer File / Create-Delete / NC list scenarios from v0.2.3
remain on the new digests.
Notes / known skips
- Worldiety segmented WPM/RPM:
upstream-deviation(B6; see bacnet-interopEVIDENCE.md) - Open execution work: B5/B5a–d (router/BBMD inventory; see bacnet-interop
BLOCKERS.md) - Library coverage gate held at ≥90%
v0.2.3
Summary
Correctness and evidence patch after v0.2.2.
No new services. Wire fixes for file / CreateObject / AtomicWriteFile ACK,
plus live-multi-peer File, Create/Delete, and NC list evidence. Pin moves to
bacnet-interop v0.6.0
@ f4ea3de. Requires Go 1.23+.
Tag: v0.2.3 @ 86ffd31
Date: 2026-08-02
Still production-candidate until the real-device gate is complete.
Fixed
EncodeAtomicReadFile/EncodeAtomicWriteFile: application ObjectIdentifier plus access CHOICE[0]stream /[1]recordEncodeAtomicWriteFileACK/DecodeAtomicWriteFileACK: context-primitive Signed[0]/[1]EncodeCreateObject/DecodeCreateObject: objectSpecifier[0]CHOICE plus listOfInitialValues[1]- Request decoders reject the pre-v0.2.3 forms; record write includes
recordCount - Codec goldens + malformed old-form fixtures via bacnet-interop
v0.6.0
Changed
- Pin → bacnet-interop v0.6.0 (
interop/bacnet-interop-pin.json)
Evidence
Green on tag SHA 86ffd31:
| Gate | Result | Link |
|---|---|---|
| Shared CI | green | 30766339592 |
Pinned interop v0.6.0 |
green | 30766339454 (Pinned release peers) |
| bacnet-interop main compat | green | same run (bacnet-interop main compat) |
| Pin | bacnet-interop v0.6.0 | interop/bacnet-interop-pin.json |
Notes / known skips
- COV-multiple live still blocked on BACnet4J 6.1.0 — B3 remainder
- Worldiety segmented WPM/RPM skipped (B6)
- B5/B6 topology and B7d–g remain open (see bacnet-interop
BLOCKERS.md) - Library coverage gate held at ≥90%
v0.2.2
v0.2.2
Next minor after v0.2.1.
Pin: bacnet-interop v0.5.0
(interop/bacnet-interop-pin.json, including Worldiety).
Added
- Worldiety as a fourth digest-pinned peer oracle (
interop/worldiety_test.go):
Who-Is/Who-Has, RP/RPM/WP/WPM, ReadRange (unsegmented) - GetAlarmSummary / GetEnrollmentSummary / SubscribeCOVPropertyMultiple
- Confirmed/Unconfirmed COVNotificationMultiple receive (+ encode helper)
- OpenEventStream / TransitionOf; OpenAuditStream
- AtomicReadFile / AtomicWriteFile (+ FileChunkBounds)
- AddListElement / RemoveListElement / CreateObject / DeleteObject
- Confirmed/Unconfirmed PrivateTransfer and TextMessage; TimeSynchronization /
UTCTimeSynchronization; WriteGroup - AuditLogQuery / AuthRequest / Who-Am-I / You-Are; audit notification receive
- LifeSafetyOperation; VT-Open / VT-Close / VT-Data
- Additional NotificationParameters typed CHOICEs (command-failure, floating-limit,
buffer-ready, unsigned-range) - Request decoders for the new confirmed/unconfirmed services
- Codec goldens for the new services via
bacnet-interop/fixtures/codec
Changed
- CI pinned interop lane pulls and runs Worldiety digests from
v0.5.0 - Interop pin file includes
worldietyimage digest
Notes / known skips
- Worldiety segmented WPM/RPM skipped (peer segment service-choice — B6)
- BACnet4J AtomicReadFile live path skipped (Error services/10 — B8)
- BACnet4J CreateObject / DeleteObject live path skipped (config — B9)
- Broader live multi-peer coverage for event/COV-multiple, file, list/lifecycle,
messaging, audit, life-safety/VT, and topology/bbmd v2 modes remains partial
(seebacnet-interop/BLOCKERS.md) - Library coverage gate held at ≥90%
v0.2.1
Date: 2026-08-02
Previous release: v0.2.0
Summary
Correctness and decoder-strictness patch on the v0.2.0 supervisory-client
surface: service-choice correlation, segmented confirmed-request overhead and
SegmentACK direction, bounded segmentation control sends, broader
outcome-unknown wrapping, EventNotification receive-path safety, and fail-closed
decoders for WPM / ReadRange / events / DCC / ReinitializeDevice. Pin moves to
bacnet-interop v0.4.2.
Requires Go 1.23+.
Still production-candidate until the
real-device gate is complete.
Fixed
- Service-choice correlation no longer treats choice
0(AcknowledgeAlarm) as
“unknown”; SimpleACK / ComplexACK / Error and every ComplexACK segment compare
unconditionally - Segmented confirmed-request first-segment overhead corrected (
6, not7) - Outbound segmented-request SegmentACK delivery requires
Server=true - Segmentation control sends (SegmentACK / Abort) use a bounded timeout
- Side-effecting services wrap Close and ambiguous transport failures after
transmission has been attempted as*OutcomeUnknownError - EventNotification handler: receive-path deadlock warning; panics recovered
- Documentation: segmented PDUs carry service choice on every segment (not an
ASHRAE “omit after segment 0” peer quirk) - Decoder strictness: WPM / ReadRange / EventNotification / DCC /
ReinitializeDevice reject duplicate fields and unsigned overflows; ResultFlags
shape checked; Log_Buffer requires successful LogRecord split; known
NotificationParameters CHOICEs fail closed on malformed contents (unknown /
unimplemented CHOICEs stay opaque)
Added
- Fuzz targets for WPM, ReadRange(+ACK), LogRecords, Who-Has/I-Have,
EventNotification, NotificationParameters, GetEventInformationACK,
AcknowledgeAlarm, DCC, ReinitializeDevice - Negative codec fixtures in
bacnet-interopfor WPM/ReadRange/DCC/Reinit
duplicates and overflow
Changed
- Pin →
bacnet-interopv0.4.2
(interop/bacnet-interop-pin.json) make fuzzdiscoversFuzz*targets viago test -listand runs each with an
anchored-fuzz='^Name$'so substring matches cannot skip or double-run
Evidence
Pre-tag green on main @ 614c4d9 (re-verify / attach on the exact tag SHA when
cutting):
| Gate | Result | Link |
|---|---|---|
| Shared CI (lint, Go matrix, race, coverage, PR fuzz) | green on 614c4d9 |
30725263974 |
Pinned interop v0.4.2 |
green | 30725263801 (job Pinned release peers) |
| bacnet-interop main compat | green | same run (job bacnet-interop main compat) |
| Pin | bacnet-interop v0.4.2 |
interop/bacnet-interop-pin.json |
Notes
- No public API break intended; behaviour and decoder strictness only.
- BACnet4J still rejects segmented confirmed-request receive (reject reason 9);
see docs/COMPATIBILITY.md.
v0.2.0
Date: 2026-08-02
Previous release: v0.1.1
Summary
Supervisory-client breadth release: WritePropertyMultiple, ReadRange (including
typed Trend Log records), Who-Has / I-Have, EventNotification receive with typed
common NotificationParameters, AcknowledgeAlarm, GetEventInformation, opt-in
DeviceCommunicationControl / ReinitializeDevice, and windowed segmented
confirmed-request send. Pin moves to
bacnet-interop v0.4.1
(BACpypes3 0.0.106). Requires Go 1.23+.
Still production-candidate until the
real-device gate is complete.
Added
- WritePropertyMultiple service codecs +
Client.WritePropertyMultiple
(SimpleACK success;*service.WritePropertyMultipleErrorfor first-failed
write; exact-APDU retransmit disabled; outcome-unknown after send) - Segmented confirmed-request send (proposed window 16,
WithSegmentWindow):
SegmentACK/NAK handling with windowed in-flight send, segment timeout → client
Abort, peer Segmentation evidence gate, MaxSegments preflight; enables large
WPM when the peer can receive segments. Segmented ComplexACK receive defaults
to actual window 1 so peers that wait for SegmentACK before the next segment
(BACpypes3 / BACnet4J) do not stall - Confirmed-request / ComplexACK codecs: segmented PDUs carry service choice on
every segment (interop with BACpypes3/BACnet4J; ASHRAE allows omitting after
segment 0) - ReadRange codecs +
Client.ReadRange(byPosition / bySequenceNumber /
byTime / all; resultFlags helpers; retransmit enabled) - Typed BACnetLogRecord split:
service.LogRecord/
ReadRangeACK.LogRecordswhen property isLog_Buffer - Who-Has / I-Have codecs +
SendWhoHas/DiscoverObjects/Objects()
with a bounded object-observation registry (shared retention options) - EventNotification receive (Confirmed/Unconfirmed) + handler API;
typed NotificationParameters for change-of-state / bitstring / value /
out-of-range (opaqueNotificationParamsretained); AcknowledgeAlarm;
GetEventInformation - DeviceCommunicationControl / ReinitializeDevice (opt-in via
WithDeviceManagementEnabled; outcome-unknown after send) - Runnable examples:
examples/read-range,examples/write-multiple,
examples/who-has,examples/events - Context helpers:
AppendContextSigned,AppendContextBitString,
AppendContextCharacterString,AppendContextTime(+ matchingContext*) - Constants:
ObjectTypeTrendLog,PropertyLogBuffer - Interop tests: three-peer WPM + Who-Has; BACpypes3 segmented WPM send;
bacnet-stack/BACnet4J ReadRange; BACpypes3/BACnet4J EventNotification receive;
three-peer ReinitializeDevice warmstart; bacnet-stack DCC enable;
BACpypes3/BACnet4J segmented RPM ComplexACK receive - Hermetic codec fixtures for the new services (via bacnet-interop)
Fixed
- Routed interop harness: bind
bip-routerby static address→network (not
Dockereth0/eth1order), which was dropping DNET forwards when iface
order swapped aftercreate+network connect
Changed
- Pin →
bacnet-interopv0.4.1
(BACpypes3 0.0.106; bacnet-stack 1.6.0; BACnet4J 6.1.0; digest-pinned GHCR images) DecodeReadRangeACKpopulatesLogRecordsfor Log_Buffer; otherwise trusts
wireitemCountwhen complex item tag streams do not 1:1 match flat items
Evidence
| Gate | Result | Link |
|---|---|---|
| Shared CI (lint, Go matrix, race, coverage, PR fuzz) | green on 8bee3e6 |
30722438815 |
Pinned interop v0.4.1 |
green | 30722438676 (job Pinned release peers) |
| bacnet-interop main compat | green | same run (job bacnet-interop main compat) |
| Pin | bacnet-interop v0.4.1 |
interop/bacnet-interop-pin.json |
Notes
- No public API break intended; additive services and options only.
- BACnet4J rejects segmented confirmed-request receive (reject reason 9);
segmented WPM send evidence uses BACpypes3. See
docs/COMPATIBILITY.md.
v0.1.1
Date: 2026-08-01
Previous release: v0.1.0
Summary
Hardening and evidence release for release-grade production-candidate
status. Bounded device-observation retention, IPv4-only B/IP endpoint
validation, strict APDU/NPDU framing, hermetic CI coverage, PR and nightly
fuzz, and pin to bacnet-interop v0.2.1
(28 fixtures including service-layer negatives). Requires Go 1.23+.
Still not production-usable until the
real-device gate is complete.
Added
WithRegistryOptions/ bounded observation registry (TTL, global cap,
per-instance path cap) with eviction diagnostics- Window-scoped
Discoverresults (Devices()remains the full snapshot) - Strict APDU/NPDU framing rejections (reserved bits, undefined MaxAPDU,
MoreFollows without SEG, invalid windows, global-broadcast DADR, router
network lists) npdu.DecodeNetworkList- CI library coverage gate (
make coverage, hermetic pinned fixtures) - CI PR fuzz and scheduled nightly fuzz (
fuzz-nightly.yml) - APDU/NPDU fuzz targets
- docs/COMPATIBILITY.md — positioning and evidence matrix
Changed
- Horizon 1
bip.Endpoint.IsValidrequires IPv4 - Inbound Distribute-Broadcast-To-Network ignored (client is not a BBMD)
- Diagnostic callbacks documented as synchronous
- Confirmed COV ACK-before-admission documented and tested
- Fixture expected-error helper supports multi-stage (APDU → service) failures
- Pin file → bacnet-interop
v0.2.1digests - PLAN / README: provisional PC until this tag; competitive positioning noted
Evidence (Batch 4D)
| Gate | Result | Link |
|---|---|---|
| Shared CI (lint, Go 1.23–1.26, race) | green on 6256fd2 |
CI run |
| Library coverage gate (≥90%) | green | same run, job Library coverage gate |
| PR fuzz (15s/target) | green | same run, job PR fuzz |
Pinned interop v0.2.1 (run 1) |
green | 30705297526 |
Pinned interop v0.2.1 (run 2) |
green | 30707779897 |
| bacnet-interop main compat | green | both interop runs |
| Nightly fuzz (5m/target, manual) | green | 30707778922 |
Local make test-race + make coverage |
90.2% ≥ 90% | operator workstation, 2026-08-01 |
Notes
- No public API break intended; additive options and stricter malformed
rejection only. - Segmented confirmed-request send, WPM, server/BBMD-server remain out of
scope (see PLAN.md Horizon 2+).
v0.1.0
Date: 2026-08-01
Previous release: none (first tagged release)
Summary
First tagged release of a pure-Go BACnet/IP supervisory client and protocol
foundation. Provides BVLC/NPDU/APDU/service codecs, a UDP client runtime for
discovery and confirmed services, COV subscriptions, routed addressing, optional
foreign-device registration, and the bacnetctl CLI. Requires Go 1.23+.
Interoperability has been exercised against bacnet-stack, BACpypes3, and
BACnet4J peers (plus a bip-router topology aid) via digest-pinned images from
bacnet-interop v0.2.0. This
is software-oracle evidence, not a claim of vendor or BTL certification.
Added
Codecs and leaf types
- BVLC — Original-Unicast / Original-Broadcast / Forwarded-NPDU, BVLC-Result,
Register-Foreign-Device encode/decode with length limits - NPDU — local and routed (
DNET/DADR,SNET/SADR) framing - APDU — confirmed/unconfirmed request, SimpleACK, ComplexACK (including
segmented receive), Error, Reject, Abort, SegmentACK - Service payloads — Who-Is / I-Am, ReadProperty / ReadPropertyACK,
ReadPropertyMultiple ACK (including per-property errors), WriteProperty
(priority and NULL relinquish), SubscribeCOV / SubscribeCOVProperty,
COV notification - Root types —
Address/MAC, object and property identifiers,
applicationValuehelpers, BACnet error class/code, decode limits
Client runtime
- UDP BACnet/IP transport on default port 47808 (
0xBAC0), with
injectable transport for tests - Who-Is / I-Am discovery and a device observation registry (max APDU,
segmentation, vendor) - ReadProperty, ReadPropertyMultiple, WriteProperty
- Confirmed-request transactions — invoke IDs, timeouts, optional
exact-APDU retransmission; abort / reject / error handling - Segmented ComplexACK reassembly and SegmentACK exchange (segmented
confirmed request send is not included) - Routed targets — router cache, Who-Is-Router-To-Network, DNET/DADR path
matching on responses - BBMD Forwarded-NPDU receive and optional foreign-device registration
(TTL renew / BVLC-Result correlation) - COV — SubscribeCOV and SubscribeCOVProperty; confirmed and unconfirmed
notifications; renew / cancel; subscription event channel with gap signalling bacnetctl— decode, discover, read, write, and version helpers
Documentation and evidence
- PROTOCOL.md — short BACnet primer mapped to this library
- API.md, ERRORS.md, INTEROP.md,
SECURITY.md - Fixture corpus consumer for
bacnet-interop
goldens (BACNET_INTEROP_ROOT) -tags=interoppeer scenarios against pinned adapter digests
(interop/bacnet-interop-pin.json)
Not in this release
- Native MS/TP, BACnet/IPv6, BACnet/SC
- Full BBMD server / BDT management / multi-BBMD failover
- Full BACnet server or device object model
- Alarms, schedules, trends, WritePropertyMultiple
- Segmented confirmed-request send
- BTL certification or vendor hardware claims
Notes
- Public APIs may still evolve in later v0.x tags; treat this as the first
usable tagged baseline, not a stability freeze. - Library unit coverage gate is 90% (CLI and Docker interop packages
excluded from the percentage).