All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Deep-dive review of the BBMD and Router implementations identified 22 spec compliance issues. All fixed.
- Fixed router forwards traffic to busy networks — now checks
effective_reachability()before forwarding and rejects with reason 2 (ROUTER_BUSY) per Clause 6.6.3.6 - Fixed router forwards traffic to permanently unreachable networks — now rejects with reason 1 per Clause 6.6.3.5
- Fixed Router-Busy-To-Network handler — now uses
mark_busy()with 30-second auto-clear timer per Clause 6.6.3.6 (was permanent until Router-Available) - Fixed Router-Available-To-Network handler — now uses
mark_available()per Clause 6.6.3.7 - Fixed Router-Busy/Available not re-broadcast to other ports — now re-broadcasts per Clause 6.6.3.6/7
- Fixed Reject-Message-To-Network removed routes — now differentiates by reason: reason 1 marks permanently unreachable (keeps entry), reason 2 marks busy with 30s timer per Clause 6.6.3.5
- Fixed unknown network message types silently dropped — now sends Reject with reason 3 (UNKNOWN_MESSAGE_TYPE) per Clause 6.6.3.5
- Added
busy_until: Option<Instant>toRouteEntryfor timestamp-based busy auto-clear - Added
effective_reachability()with inline deadline check (avoids 90-second worst-case from sweep granularity) - Added
mark_busy(),mark_available(),mark_unreachable(),clear_expired_busy()toRouterTable - Added message-too-long framework —
max_apdu_lengthcaptured per port for future size validation
- Fixed I-Am-Router-To-Network not re-broadcast when no new routes learned — now re-broadcasts unconditionally per Clause 6.6.3.3
- Fixed anti-flapping logic blocked spec-required route updates from different ports — replaced
add_learned_stablewithadd_learned_with_flap_detectionthat always accepts updates per Clause 6.6.3.2 ("last message wins") but logs rapid changes for operator visibility - Fixed
touch()never called — learned routes now refreshed on every route lookup during forwarding, preventing active routes from being purged by the 5-minute aging sweep - Added flap detection fields (
flap_count,last_port_change) toRouteEntryfor observability
- Added Initialize-Routing-Table-Ack handler — learns routes from peer ACK responses per Clause 6.4.8
- Added Network-Number-Is handler — detects and logs network number conflicts per Clause 6.6.3.12
- Added explicit match arm for security messages (0x0A-0x11) — prevents incorrect rejection
- Changed Establish-Connection-To-Network log level from
debugtoinfowith "not implemented" note
- Fixed BBMD not included in its own BDT —
ensure_self_in_bdt()auto-inserts local BBMD entry onset_bdt()per J.4.2 - Fixed non-BBMD Forwarded-NPDU uses wrong source_mac — now uses originating address from frame (spec J.2.5) instead of UDP sender address; fixes cross-BBMD unicast for non-BBMD nodes
- Fixed non-BBMD silently drops Distribute-Broadcast-To-Network — now sends NAK (0x0060) per J.4.5
- Fixed Register-Foreign-Device with empty payload silently defaults TTL=0 — now validates payload >= 2 bytes and NAKs if short
- Added BDT persistence — optional file-backed persistence via
set_bdt_persist_path()using BDT wire encoding (no serde dependency) - Improved
forward_npduyields every 32 sends to avoid starving the recv loop with large FDT (up to 512 entries)
- Fixed invoke ID leak on task cancellation —
TsmGuarddrop guard inconfirmed_request_innercleans up invoke IDs if the tokio task is aborted before normal completion
Deep-dive review of all five transport implementations (BIP, BIPv6, BACnet/SC, Ethernet, MS/TP) identified 34 spec compliance issues. All addressed (31 fixed, 3 deferred as future features).
- Fixed IDLE state timeout used T_usage_timeout (20ms) instead of T_no_token (500ms) — node declared token lost 25x too quickly, causing premature token generation and bus collisions
- Fixed WAIT_FOR_REPLY did not transition to DONE_WITH_TOKEN on receiving a reply — added ~255ms unnecessary latency to every confirmed MS/TP request
- Fixed NoToken entry from PassToken timeout missing T_slot*TS per-station offset — multiple stations could simultaneously generate tokens
- Fixed no source address validation on reply frames in WAIT_FOR_REPLY — a frame from the wrong station could be incorrectly accepted as a reply
- Fixed ReplyPostponed frames (type 0x07) silently discarded — now transitions to DONE_WITH_TOKEN per Clause 9.5.6
- Added T_frame_abort tracking — discards partial frames when inter-byte gap exceeds 60 bit times per Clause 9.3
- Added
expected_reply_sourcefield toMasterNodefor reply frame validation
- Fixed Virtual-Address-Resolution wire format — was 10 bytes with duplicate VMAC payload, now 7 bytes per Clause U.2.7
- Fixed Virtual-Address-Resolution-ACK — now accepts and encodes requester's destination VMAC (10 bytes per Clause U.2.7A)
- Fixed
send_unicastderived destination VMAC from IPv6 address bytes — now uses VMAC address table reverse lookup per Clause U.5 - Fixed decoder only extracted destination VMAC for OriginalUnicast — now also extracts for AddressResolution, AddressResolutionAck, VirtualAddressResolutionAck
- Fixed
derive_vmac_from_device_instancedid not mask to 22 bits per Clause H.7.2 - Added
VmacTable— VMAC-to-address mapping with learn-on-receive from all incoming frames per Clause U.5 - Added Address-Resolution and Address-Resolution-ACK handlers in recv loop
- Added
Bip6BroadcastScopeenum — configurable broadcast multicast scope (LinkLocal/SiteLocal/OrganizationLocal), default SiteLocal - Added
Bip6ForeignDeviceConfig— foreign device registration with TTL/2 re-registration, Distribute-Broadcast-To-Network in FD mode - Added BVLC-Result handling in recv loop with NAK logging
- Fixed HeartbeatAck included unnecessary originating/destination VMACs — now omitted per AB.2.11
- Fixed BVLC-Result parsing used payload-presence heuristic — now properly parses Result Code byte (0x00=ACK, 0x01=NAK) with error class/code extraction
- Fixed ConnectAccept message_id not verified against ConnectRequest — now rejects mismatched responses per AB.3.1.3
- Fixed
stop()aborted task without DisconnectRequest — now sends DisconnectRequest with 2-second timeout before abort, clears shared state - Added Device UUID parsing from ConnectAccept payload (bytes 6..22), stored as
hub_device_uuid - Added
build_heartbeat_ack()method onScConnection(extracted from inline recv loop construction) - Added
pending_connect_message_idfield for response verification
- Fixed ConnectRequest accepted with >= 6 bytes — now requires exactly 26 bytes per AB.2.9, NAKs short payloads with MESSAGE_INCOMPLETE
- Fixed pre-handshake messages silently dropped — now returns BVLC-Result NAK
- Fixed unknown function codes silently ignored — now returns BVLC-Result NAK
- Fixed broadcast relay was sequential — now parallel via
join_allwith per-client 5-second timeout - Added per-client
max_npdutracking from ConnectRequest — oversized NPDUs rejected on unicast relay - Added hub heartbeat initiation — periodic sweep (30s interval, 60s idle threshold) sends HeartbeatRequest to idle clients, removes clients on send failure
- Added
HubClientstruct withsink,max_npdu,last_activityfields - Added
build_bvlc_result_nak()helper for consistent NAK construction
- Changed all
ClientConfigbuilders to usebuilder_with_protocol_versions(&[&TLS13])— spec requires TLS 1.3
- Added XID and TEST command/response handling — Clause 7.1 "shall" requirement
- Added
build_xid_response()andbuild_test_response()frame builders - Added
check_llc_control()helper for raw LLC control byte inspection - Changed BPF filter widened to accept UI, XID, and TEST control bytes (was UI only)
- Fixed recv loop broke permanently on any error — now classifies transient (EAGAIN, EINTR, ENOBUFS) vs fatal errors
- Improved BVLC-Result NAK handling — REGISTER_FOREIGN_DEVICE_NAK and DISTRIBUTE_BROADCAST_TO_NETWORK_NAK now logged at error level with specific messages
- Fixed BIP, BIP6, Ethernet
start()leaked recv task and socket on double call — now returns error viaOption::take()guard, matching SC/MS/TP/Loopback pattern - Fixed MS/TP and SC used
Error::Encodingfor "transport not started" — now usesError::Transport(NotConnected), matching BIP/BIP6/Ethernet - Fixed BIP, BIP6, Ethernet recv loops used
.awaiton bounded channel send — now usestry_send()with warn log, preventing recv loop stall on slow consumers - Fixed MS/TP
stop()left node queue and state intact — now clears queue and resets state - Fixed SC
stop()leftws_sharedandconnectionalive — now clears after disconnect - Added named
NPDU_CHANNEL_CAPACITYconstants in all transports (256 for BIP/BIP6/Ethernet/Loopback, 64 for SC/MS/TP) with documented rationale - Changed
bip6module feature-gated behindipv6feature flag — consistent withethernetandsc-tlsgating; propagated to bacnet-client, bacnet-java, bacnet-btl, bacnet-cli, benchmarks
Deep-dive review of encoding, types, services, objects, client, server, and network layers identified 43 spec compliance issues. All critical/high/medium fixed.
- Fixed SegmentAck window size not clamped to 1-127 range on decode — now clamps with warning log per Clause 20.1.6
- Fixed reserved max_apdu values silently accepted — now logs warning for non-standard values
- Fixed LifeSafetyOperation enum ordering — reset=4, reset-alarm=5, reset-fault=6, unsilence=7 per Table 12-54
- Added LifeSafetyMode OEO values (15-19) per 135-2020 addendum
- Added DaysOfWeek bitflags type for schedule encoding
- Added 11 new BACnetPropertyStates variants (UnsignedValue, DoorAlarmState, Action, DoorSecuredStatus, DoorStatus, DoorValue, TimerState, TimerTransition, LiftCarDirection, LiftCarDoorCommand)
- Fixed TextMessage tags — messagePriority and message use context tags [2] and [3] (were [3] and [4])
- Fixed ReinitializeDevice password validation — SIZE(1..20) per Clause 16.4.1.1.5
- Added
message_text: Option<String>field to EventNotificationRequest with encode/decode per Clause 13.8.1 - Added
RecipientProcessstruct andenrollment_filterfield to GetEnrollmentSummaryRequest
- Fixed StatusFlags IN_ALARM never set — all 9 event-capable object types (AI/AO/AV/BI/BO/BV/MSI/MSO/MSV) now compute IN_ALARM from
event_detector.event_state - Added
compute_status_flags()helper function for consistent StatusFlags computation across object types - Added ValueSourceTracking fields (VALUE_SOURCE, LAST_COMMAND_TIME) to AV, BO, BV, MSO, MSV
- Added
set_overridden()default method on BACnetObject trait
- Fixed per-window SegmentAck — tracks window position for correct sequence acknowledgment
- Fixed duplicate segment handling in segmented response reassembly
- Fixed negative SegmentAck uses
wrapping_sub(1)for correct sequence arithmetic - Added Abort on unsupported segmented response when
segmented_response_acceptedis false - Added
segmented_response_acceptedparameter threading through dispatch_apdu/handle_segmented_complex_ack - Added device table auto-purge every 5 minutes for stale entries
- Fixed COV notification
ack_requiredflag —notify_type == NotifyType::ALARM(was!= ACK_NOTIFICATION) - Fixed DCC DISABLE now accepted — all 3 EnableDisable values work correctly per 135-2020
- Fixed COVProperty cancel now calls
unsubscribe_property()instead ofunsubscribe() - Fixed RPM handler resolves device wildcard via
resolve_device_wildcard() - Fixed GetEnrollmentSummary priority lookup reads from notification class object (was hardcoded 0)
- Fixed intrinsic reporting silently non-functional — EVENT_ENABLE stored as BitString but read via
read_unsigned(); addedread_event_enable()helper handling both types - Fixed schedule tick passes UTC offset parameter for correct time computation
- Fixed EventNotificationRequest now includes
message_text: Nonefield - Added
days_to_date()helper for full datetime in trend log records
- Fixed remote broadcast self-delivery — router now delivers broadcast to local network layer
- Fixed
is_network_messagepassthrough in routing (was hardcoded false) - Fixed proprietary network messages (type >= 0x80) with DNET now forwarded correctly
- Fixed Init-Routing-Table-Ack uses actual port_index (was hardcoded)
- Rewritten
.pyitype stubs from scratch (826 → 1598 lines) — all 47 client methods, 62+ server methods, correct exception names, CovNotification class, PropertyValue constructors, all 65 ObjectType constants - Added
time_synchronization()andutc_time_synchronization()methods - Added
who_is_directed()for unicast WhoIs - Added auto-routing methods:
read_property_from_device(),read_property_multiple_from_device(),write_property_to_device(),write_property_multiple_to_device() - Added
add_device()for manual device table population - Added
discover(timeout_ms)convenience method — combines WhoIs + sleep + discovered_devices - Added
PropertyValue.date(),.time(),.bit_string(),.list()static constructors - Added structured error attributes —
BacnetProtocolError.error_class/.error_code,BacnetRejectError.reason,BacnetAbortError.reason - Added
dcc_passwordandreinit_passwordparameters toBACnetServerconstructor
- New crate:
bacnet-gateway— HTTP REST API and MCP (Model Context Protocol) server for BACnet networks- REST API at
/api/v1/with endpoints for device discovery, property read/write, local object CRUD, and health check - MCP server at
/mcpwith 10 tools for LLM-driven BACnet interaction - MCP reference knowledge base — 9 static resources plus per-object-type drill-down templates
- Pluggable authentication with bearer token, TOML configuration with CLI overrides
- Feature-gated:
http,mcp,bin— zero web deps by default
- REST API at
LoopbackTransportinbacnet-transport— in-process transport for gateway client/server composition- RS-485 GPIO direction control —
GpioDirectionPort<S>wrapper with configurablepost_tx_delay_us, kernel RS-485 ioctl onTokioSerialPort - Client batch operations —
read_property_from_devices(),read_property_multiple_from_devices(),write_property_to_devices()withbuffer_unordered(max_concurrent)for concurrent multi-device I/O - Client auto-routing —
resolve_device()helper +_from_devicevariants for RP, RPM, WP, WPM - Server concurrent dispatch — spawns per-request tasks for ConfirmedRequest/UnconfirmedRequest, enabling concurrent
db.read()from multiple clients - Architecture documentation —
docs/architecture.md, expandeddocs/rust-api.md,docs/gateway.md,docs/btl.md,docs/wasm-api.md
- Dependencies updated — criterion 0.5→0.8, tokio-tungstenite 0.28→0.29, rand 0.9→0.10, rustyline 15→17, toml 0.8→1.0, rcgen 0.13→0.14, aws-lc-sys 0.38→0.39, rustls-webpki 0.103.9→0.103.10
- Security advisories resolved — aws-lc-sys X.509 name constraints bypass, CRL distribution point logic errors; rustls-webpki CRL scope check
- Java/Kotlin bindings — removed
bacnet-javacrate,uniffi-bindgencrate,java/Gradle project,examples/kotlin/, and all associated CI jobs (no user base; maintenance burden)
- New crate:
bacnet-gateway— HTTP REST API and MCP (Model Context Protocol) server for BACnet networks- REST API at
/api/v1/with endpoints for device discovery, property read/write, local object CRUD, and health check - MCP server at
/mcpwith 10 tools for LLM-driven BACnet interaction (discover_devices, read/write_property, list/read/write/create/delete local objects) - MCP reference knowledge base — 9 static resources teaching BACnet concepts (object types, properties, units, errors, reliability, priority array, networking, services, troubleshooting) plus per-object-type drill-down templates
- 3 live state MCP resources (devices, local-objects, config)
- Pluggable authentication with bearer token default, applied to both REST and MCP endpoints
- TOML configuration with CLI overrides, config validation (reserved network numbers, mutual exclusivity checks)
- Feature-gated binary (
--features bin) with graceful shutdown, tracing,--no-mcp/--no-apiflags - 13 supported object types for local creation (analog/binary/multi-state I/O/V, integer, large-analog, positive-integer, characterstring values)
- REST API at
LoopbackTransportinbacnet-transport— in-process transport backed by mpsc channels for gateway client/server compositionAnyTransport::Loopbackvariant for mixed-transport routing with loopback ports
- Fixed maturin wheel build — removed invalid
python-sourcesetting from pyproject.toml that broke wheel builds for pure Rust extension module
Comprehensive 7-area compliance review and 55+ fixes across the entire protocol stack.
- Fixed control flag bit positions — was using bits 7-4 instead of spec's bits 3-0
- Fixed ConnectRequest/ConnectAccept payload — added 16-byte Device UUID (now 26 bytes per AB.2.10.1)
- Fixed removed VMACs from ConnectRequest, ConnectAccept, DisconnectRequest, DisconnectAck, HeartbeatRequest, HeartbeatAck (spec says 0-octets)
- Fixed BVLC-Result NAK format — added result_code byte and error header marker (7+ bytes per AB.2.4.1)
- Fixed hub relay — now rewrites Originating Virtual Address and strips Destination Virtual Address for unicast (AB.5.3.2/3)
- Fixed header option encoding — proper Must Understand (bit 6) and Header Data Flag (bit 5) handling per AB.2.3
- Fixed broadcast VMAC — removed all-zeros as broadcast (X'000000000000' is reserved/unknown per AB.1.5.2)
- Fixed non-binary WebSocket frames — now closed with status 1003 per AB.7.5.3
- Fixed reconnect minimum delay — 10s min, 600s max per AB.6.1
- Fixed Bvlc6Function codes — 0x0B removed per Table U-1, 0x0C = Distribute-Broadcast-To-Network
- Fixed Bvlc6ResultCode values — corrected from sequential 0x10 increments to spec values (0x0060, 0x0090, 0x00A0, 0x00C0)
- Fixed Original-Unicast-NPDU — added 3-byte Destination-Virtual-Address (10-byte header per U.2.2.1)
- Fixed Forwarded-NPDU — added 18-byte Original-Source-B/IPv6-Address (25-byte header per U.2.9.1)
- Fixed FDT seconds_remaining — now includes 30-second grace period per J.5.2.3
- Increased BIP6 recv buffer from 1536 to 2048 bytes
- Fixed I-Am-Router-To-Network — now sent as broadcast per Clause 6.4.2 (was unicast)
- Fixed router final-hop delivery — strips DNET/DADR/HopCount per Clause 6.5.4
- Fixed SNET=0xFFFF rejected on decode per Clause 6.2.2.1
- Fixed non-router now discards DNET-addressed messages per Clause 6.5.2.1
- Fixed reject reason — uses NOT_DIRECTLY_CONNECTED (1) instead of OTHER (0) per Clause 6.6.3.5
- Fixed What-Is-Network-Number ignores routed messages per Clause 6.4.19
- Added I-Am-Router re-broadcast to other ports per Clause 6.6.3.3 (with loop prevention)
- Added Who-Is-Router forwarding for unknown networks per Clause 6.6.3.2
- Added SNET/DNET validation at encode time
- Added reserved network numbers (0, 0xFFFF) rejected in routing table
- Added reachability status (Reachable/Busy/Unreachable) to RouteEntry per Clause 6.6.1
- Added Router-Busy/Router-Available messages update reachability status per Clause 6.6.4
- Added Reject-Message-To-Network relay to originating node per Clause 6.6.3.5
- Added Init-Routing-Table count=0 query returns full table without updating per Clause 6.4.7
- Fixed Property_List — excludes Object_Identifier, Object_Name, Object_Type, Property_List per Clause 12.1.1.4.1
- Fixed StatusFlags — now dynamically computed from event_state, reliability, out_of_service
- Fixed Object_Name — now writable on all object types per Clause 12.1.1.2
- Added Device_Address_Binding to Device object (required per Table 12-13)
- Added Max_Segments_Accepted to Device object (required when segmentation supported)
- Added Current_Command_Priority to all commandable objects (AO, BO, MSO, AV, BV, MSV)
- Added ChangeOfStateDetector for binary and multi-state objects (Clause 13.3.1)
- Added CommandFailureDetector for commandable output objects (Clause 13.3.3)
- Added Event_Time_Stamps and Event_Message_Texts to analog objects
- Added Alarm_Values and Fault_Values to multi-state objects
- Added ValueSourceTracking (Value_Source, Last_Command_Time) to commandable objects
- Fixed SubscribeCOV lifetime=0 — now means indefinite per Clause 13.14.1.1.4 (was immediate expiry)
- Fixed TextMessage messageClass — uses constructed encoding (opening/closing tag) per Clause 16.5
- Fixed AcknowledgeAlarm — added time_of_acknowledgment parameter (tag [5]) per Table 13-9
- Fixed DCC DISABLE (value 1) — rejected per 2020 spec Clause 16.1.1.3.1 (deprecated)
- Fixed DCC password length — validated ≤ 20 characters per Clause 16.1.1.1.3
- Fixed SubscribeCOV — verifies object supports COV per Clause 13.14.1.3.1
- Fixed ReadRange count=0 — rejected per Clause 15.8.1.1.4.1.2
- Fixed ReadRange ByPosition — returns empty result for out-of-range indices per Clause 15.8.1.1.4.1.1
- Fixed WriteGroup — group_number=0 rejected per Clause 15.11.1.1.1
- Fixed RPM — encode failure produces per-property error instead of aborting response
- Fixed GetEventInformation — reads actual event timestamps when available
- Fixed COV subscription key — includes monitored_property (per-property and whole-object subs coexist)
- Fixed T_slot — fixed to 10ms per Clause 9.5.3 (was incorrectly computed from baud rate)
- Fixed INITIALIZE state — NS=TS, PS=TS, TokenCount=N_poll per Clause 9.5.6.1
- Fixed ReceivedToken — clears SoleMaster per Clause 9.5.6.2
- Added PassToken state with retry/FindNewSuccessor per Clause 9.5.6.6
- Added DONE_WITH_TOKEN proper logic (sole master, maintenance PFM, NextStationUnknown)
- Fixed WaitForReply timeout — transitions to DoneWithToken per Clause 9.5.6.4
- Added NO_TOKEN T_slot*TS priority arbitration per Clause 9.5.6.7
- Fixed PollForMaster ReceivedReplyToPFM — sends Token to NS, enters PassToken per Clause 9.5.6.8
- Added EventCount tracking per Clause 9.5.2
- Added T_turnaround enforcement per Clause 9.5.5.1
- Fixed window size — clamped to 1-127 on encode per Clauses 20.1.2.8, 20.1.5.5, 20.1.6.5
- Fixed 256-segment edge case — now allows 256 segments (sequence 0-255) per Clause 20.1.2.7
- Fixed character set names — IBM_MICROSOFT_DBCS (was JIS_X0201), JIS_X_0208 (was JIS_C6226) per Clause 20.2.9
- Added separate APDU_Segment_Timeout field in TSM config per Clause 5.4.1
New bacnet-btl crate — a full BTL Test Plan 26.1 compliance test harness with 3808 tests across all 13 BTL sections, 100% coverage of all BTL test references.
- New crate
bacnet-btlwithbacnet-testbinary — self-test, external IUT testing, interactive shell - 3808 tests organized across 13 BTL sections (s02–s14), one directory per section
self-testcommand — in-process server with all 64 object types, runs full suite in <1sruncommand — tests against external BACnet device over BIP or BACnet/SCservecommand — runs the full BTL object database as a standalone server (BIP or SC)- SC client/server support — feature-gated behind
sc-tls, includes self-signed cert generation - Docker support —
Dockerfile.btlanddocker-compose.btl.ymlwith SC hub + BIP + routing topologies - RPM/WPM test helpers —
read_property_multiple,rpm_all,rpm_required,rpm_optional,write_property_multiple,wpm_singleon TestContext
- Added EVENT_STATE to AccessDoor, LoadControl, Timer, AlertEnrollment objects
- Added Device properties: LOCAL_DATE, LOCAL_TIME, UTC_OFFSET, LAST_RESTART_REASON, DEVICE_UUID
- Added Schedule PRIORITY_FOR_WRITING property
- Added Device wildcard instance (4194303) support in ReadProperty/ReadPropertyMultiple handlers
- Added PROPERTY_IS_NOT_AN_ARRAY error in ReadProperty handler
- Added AccessDoor full command prioritization (priority array write, NULL relinquish)
- Added
supports_cov() = trueon 11 additional object types (LifeSafetyPoint, LifeSafetyZone, AccessDoor, Loop, Accumulator, PulseConverter, LightingOutput, BinaryLightingOutput, Staging, Color, ColorTemperature) - Fixed AccumulatorObject
supports_cov()was on wrong impl block (PulseConverterObject) - Added EVENT_ENABLE, ACKED_TRANSITIONS, NOTIFICATION_CLASS, EVENT_TIME_STAMPS to Binary and Multistate objects
- Added EVENT_ENABLE, NOTIFICATION_CLASS to Multistate Input/Output/Value objects
- Changed DatePatternValue, TimePatternValue, DateTimePatternValue from
define_value_object_simple!todefine_value_object_commandable!(per BTL spec, all value types are commandable) - Added LightingOutput DEFAULT_FADE_TIME property
- Added Staging PRESENT_STAGE, STAGES properties
- Added NotificationForwarder RECIPIENT_LIST, PROCESS_IDENTIFIER_FILTER properties
- Added Lift FLOOR_NUMBER property
- New Color object (type 63) — full implementation with CIE 1931 xy coordinates
- New ColorTemperature object (type 64) — full implementation with Kelvin value
- Added Device dynamic Protocol_Object_Types_Supported bitstring calculation (auto-detects all object types in database)
- Fixed client segmented send panic — validates SegmentAck sequence_number bounds (was unchecked index)
- Fixed silent u16 truncation in BVLL, BVLC6, and SC option encode functions (added overflow checks)
- Fixed silent u32 truncation in primitives encode functions (octet_string, bit_string)
- Fixed server dispatch
expect()— replaced with graceful error handling (prevented server crash)
- Fixed I-Am-Router broadcast loop — only re-broadcasts newly learned routes
- Fixed Init-Routing-Table — enforces MAX_LEARNED_ROUTES cap, validates info_len bounds
- Fixed routing table — rejects reserved network numbers (0, 0xFFFF), add_learned won't overwrite direct routes
- Added SC Hub pre-handshake connection limit (512 max) to prevent DoS
- Added SC Hub rejects reserved VMACs (unknown/broadcast) on ConnectRequest
- Fixed BDT size validation — returns error instead of panicking
- Fixed TLS WebSocket lock ordering — drops read lock before acquiring write lock in recv()
- Fixed SC Hub broadcast relay — sequential sends with per-client timeout (was unbounded task spawning)
- Fixed COV polling — replaced 50ms polling loop with oneshot channels for instant delivery
- Fixed COV subscription key — includes monitored_property (per-property subs no longer overwrite whole-object subs)
- Fixed DeleteObject — now cleans up COV subscriptions for deleted objects
- Fixed event notification invoke_id — uses ServerTsm allocation (was hardcoded 0)
- Fixed day-of-week calculation — consistent 0=Monday convention across schedule.rs and server.rs
- Fixed COV notification content — sends only monitored property for SubscribeCOVProperty subscriptions
- Added route.port_index bounds check before indexing send_txs
- Added duplicate port network number detection at router startup
- Added checked_add in decode_error and decode_timestamp offset arithmetic
- Added ObjectIdentifier debug_assert on encode for type/instance overflow
- Added is_finite debug_assert in analog set_present_value
- Added transition_bit mask (& 0x07) in acknowledge_alarm
- Added messageText skip loop iteration limit
- Added GetAlarmSummary handler — iterates objects, returns those with event_state != NORMAL
- Added GetEnrollmentSummary handler — with filtering by acknowledgment, event state, priority, notification class
- Added ConfirmedTextMessage handler
- Added UnconfirmedTextMessage handler
- Added LifeSafetyOperation handler
- Added WriteGroup handler
- Added SubscribeCOVPropertyMultiple handler — creates per-property COV subscriptions
- Wired all new handlers into server dispatch (GetAlarmSummary, GetEnrollmentSummary, TextMessage, LifeSafetyOperation, SubscribeCOVPropertyMultiple, WriteGroup)
- Added
rusty_bacnet.pyitype stub file — full type introspection for IDEs (VS Code, PyCharm) - Added
py.typedmarker (PEP 561) for mypy/pyright support - Type stubs cover: 10 enum classes, 4 core types, 3 exception classes, BACnetClient (35+ async methods), BACnetServer (50+ methods), ScHub
- Moved trend_log polling state out of global static into server struct
- Cleaned up QUIC research branch and artifacts
- LoopbackSerial now buffers excess bytes instead of silently truncating
- Init-Routing-Table ACK only encodes up to count entries (prevents payload/count mismatch)
- BIP transport refactor:
RecvContextstruct replaces 9-parameterhandle_bvll_messagesignature (removesclippy::too_many_arguments) - Confirmed request refactor:
ConfirmedTargetenum deduplicatesconfirmed_requestandconfirmed_request_routedinto sharedconfirmed_request_inner - BBMD deferred initialization:
enable_bbmd()storesBbmdConfiginstead of creatingBbmdStatewith dummy address; real state created atstart()with actual bound address - Foreign device registration clarity:
register_foreign_devicerenamed toregister_foreign_device_bvlcto distinguish BVLC-only post-start registration from pre-startregister_as_foreign_device(which enables Distribute-Broadcast-To-Network) - Extracted
require_socket()helper to deduplicate socket-not-started error insend_unicast/send_broadcast - Updated
NetworkLayerdoc comments to clarify non-router role with DNET/DADR addressing capability
- BVLC concurrency guard:
bvlc_requestrejects concurrent management requests (returns error instead of silently overwriting pending sender) - Documented Forwarded-NPDU source_mac asymmetry (BBMD mode vs foreign device mode) in BIP transport
- Interactive shell session state:
targetcommand to set/show/clear default target address;statuscommand shows transport, local address, BBMD registration, and discovered device count - BBMD auto-renewal in interactive shell:
registerstores registration and spawns background task to renew at 80% TTL;unregistercancels renewal; shown instatusoutput - Missing shell commands now available interactively:
ack-alarm/ack,time-sync/ts,create-object,delete-object,read-range/rr - Shell
discover --bbmd: register as foreign device and discover in one step (BIP shell only) - Colored terminal output via
owo-colors: green for success/values, red for errors, yellow for warnings, cyan for addresses, dimmed for metadata - Default target auto-prepend: commands like
read,write,subscribeuse the session default target when no target argument is given - Discovery progress feedback: "Waiting Ns for responses..." status line during WhoIs
- Deduplicated
format_mac()anddevice_info()intooutput.rs(removed fromdiscover.rsandrouter.rs) - Removed dead code:
is_tty(),print_ok(),print_value()from output module - BIP shell separated from generic shell for type-safe BBMD command dispatch
- Interactive
discover --targetreturning "No devices found" on subsequent calls (removed stale HashSet filter) - Unused import
use bacnet_encoding::npdu::Npduinbacnet-network/src/layer.rs
- CLI
discoveroptions for cross-subnet and directed device discovery:--target <addr>— send directed (unicast) WhoIs to a specific device or router--bbmd <addr>— register as foreign device with a BBMD before discovering (with--ttl)--dnet <network>— target a specific remote network number through a router
BACnetClient::who_is_directed()— unicast WhoIs to a specific MAC addressBACnetClient::who_is_network()— WhoIs broadcast to a specific remote networkNetworkLayer::broadcast_to_network()— broadcast APDU to a specific DNET (not global 0xFFFF)- Shell
discovercommand supports--targetand--dnetflags
- Interactive shell backspace not visually deleting characters (set
Behavior::PreferTermin rustyline)
bacnet capturecommand for live packet capture and offline pcap file analysis- BACnet frame decoder (BVLC/NPDU/APDU) with summary and full decode modes (
--decode) --save/--readflags for pcap file I/O,--quietfor headless recording--filterflag for additional BPF filter expressions (appended to default BACnet filter)pcapfeature flag onbacnet-cli(off by default, requires libpcap)- Pre-built CLI binaries for Linux (amd64/arm64, with pcap), macOS (amd64/arm64), Windows (amd64)
- CLI reference documentation (
docs/CLI.md)
- Replaced stale
nicegatesorg references withjscott3201across README, Java packaging, and npm config
- BBMD client API (Annex J):
read_bdt(),write_bdt(),read_fdt(),delete_fdt_entry(),register_foreign_device()onBipTransportwith oneshot channel response correlation - BACnet/IPv6 CLI support (Annex U):
--ipv6,--ipv6-interface,--device-instanceflags;Bip6ClientBuilderandbip6_builder()onBACnetClient - IPv6 bracket-notation target parsing (
[::1]:47808,[fe80::1]) in CLI FdtEntryWirestruct anddecode_fdt()/encode_bdt_entries()helpers inbacnet-transportBACnetClient<BipTransport>delegate methods for BBMD management- CLI
bdt,fdt,register,unregistercommands now fully functional (were stubs) - Table and JSON output for BDT/FDT query results via
comfy-table
- BBMD management commands restricted to BIP transport (clear error on SC/IPv6)
- BIP transport dispatch in CLI separated into
execute_bip_command()for type-safe BIP-only operations
- Java/Kotlin Gradle publish URL pointed to wrong GitHub org
- WASM npm package missing
repositoryfield — npm provenance verification requires it
- Clippy
io_other_errorlint in ethernet transport (Rust 1.93) - Clippy
inherent_to_stringlint in WASM bindings — useDisplaytrait instead
- Added CHANGELOG entries for patch releases to unblock CI release pipeline
- Clippy
io_other_error: usestd::io::Error::other()in ethernet transport
- Kotlin examples: 4 examples (BIP client/server, COV subscriptions, device management, IPv6)
- JMH benchmarks: full Kotlin/JVM benchmark suite (BIP ops, concurrency, JNA overhead, object creation)
- Benchmarks.md: added Kotlin/JVM benchmark results section
- Clippy
inherent_to_stringinbacnet-wasm— implementedDisplayforJsObjectIdentifier - UniFFI
async_runtime = "tokio"on all async impl blocks (fixes "no reactor running" in JMH) - Stale benchmarks crate version (0.4.0 → 0.5.x)
- Updated CLAUDE.md with Java/Kotlin build commands, UniFFI conventions, workspace layout
- Java/Kotlin bindings: new
bacnet-javacrate via UniFFI 0.31BacnetClientwith 23 async methods (ReadProperty, WriteProperty, RPM, WPM, COV, WhoIs, IAm, etc.)BacnetServerwith 60+ object type builders (all ASHRAE 135-2020 standard object types)CovNotificationStreamasync iterator for real-time COV notifications- Transport factory supporting BIP/IPv4, BIP6/IPv6, and MS/TP configurations
- Error mapping from internal BACnet errors to typed Kotlin exceptions
- 36 unit tests covering all bindings
- Java/Kotlin distribution: multi-platform JAR with JNA native libraries
- Platforms: Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64
- Published to GitHub Packages as
io.github.jscott3201:bacnet-java - Kotlin suspend functions for async operations via kotlinx-coroutines
- Gradle build with
build-local.shfor development workflow
- CI: Java native library builds across 5 platform matrix
- CI: Automated JAR packaging and GitHub Packages publishing on release tags
- CI: JAR attached to GitHub Releases
- WASM/JavaScript support: new
bacnet-wasmcrate for BACnet/SC thin client in browsersBACnetScClientclass withconnect,disconnect,readProperty,writeProperty,whoIs,subscribeCov- SC frame codec and connection state machine ported from bacnet-transport (pure computation, no tokio)
- Browser WebSocket adapter via
web-syswith async recv/send - Service codec functions:
encodeReadProperty,encodeWriteProperty,encodeWhoIs,encodeSubscribeCov - Value encoding helpers:
encodeReal,encodeUnsigned,encodeBoolean,encodeEnumerated - APDU decoder returning JS objects:
decodeApdu,decodeReadPropertyAck - JS-facing type wrappers:
JsObjectIdentifier,ObjectTypes,PropertyIdsconstants - TypeScript definitions auto-generated by wasm-bindgen
- 40 native unit tests for SC frame codec, connection state machine, and service codecs
- CI: WASM compilation check (
wasm32-unknown-unknown) on every push - CI: wasm-pack build (bundler + web targets) and npm publish on release tags
- CI: use native ARM64 runner (
ubuntu-24.04-arm) for aarch64 Linux wheels instead of cross-compilation
- Moved client ↔ server integration tests into
bacnet-integration-testscrate, breaking the circular dev-dependency that prevented publishing to crates.io
- CI: added QEMU emulation for aarch64 Linux wheel builds (fixes aws-lc-sys cross-compilation failure)
- CI: disabled sccache for emulated aarch64 builds
- CI: simplified crates.io publish ordering (server before client, no retry hack)
- CI: fixed circular dev-dependency publish ordering (bacnet-server ↔ bacnet-client)
- CI: fixed aarch64 Linux wheel builds (install cmake/perl for aws-lc-sys cross-compilation)
- Crate publishing: added version to all workspace dependency declarations
- Cargo Deny: added MPL-2.0 to allowed licenses for
serialportdependency - Ethernet transport: added missing
TransportPorttrait import in test module
Initial release of the Rusty BACnet protocol stack implementing ASHRAE 135-2020.
- Complete BACnet protocol stack: application, network, and transport layers
- ASN.1/BER encoding and decoding for all BACnet data types
- APDU segmentation (both send and receive) with configurable segment sizes
- NPDU routing with hop-count loop prevention
- BACnet/IP (BIP) — UDP transport with broadcast support (Annex J)
- BACnet/IPv6 (BIP6) — Multicast transport with 3-byte VMAC and 3 multicast scopes (Annex U)
- BACnet/SC — Secure Connect over WebSocket + TLS with mutual authentication (Annex AB)
- BACnet/SC Hub — Hub-and-spoke relay for SC topology with VMAC collision detection
- MS/TP — Master-Slave Token Passing over RS-485 serial (Annex G, Linux only)
- Ethernet — 802.3 with BPF filtering (Annex K, Linux only)
- BBMD — BACnet Broadcast Management Device with Foreign Device Table and management ACL
- Property Access: ReadProperty, WriteProperty, ReadPropertyMultiple, WritePropertyMultiple
- Object Management: CreateObject, DeleteObject
- Discovery: WhoIs/IAm, WhoHas/IHave, WhoAmI
- COV: SubscribeCOV, SubscribeCOVProperty, SubscribeCOVPropertyMultiple, COV notifications (confirmed and unconfirmed)
- File Access: AtomicReadFile, AtomicWriteFile
- Alarm & Event: AcknowledgeAlarm, GetAlarmSummary, GetEnrollmentSummary, GetEventInformation
- Device Management: DeviceCommunicationControl, ReinitializeDevice, TimeSynchronization, UTCTimeSynchronization
- List Operations: AddListElement, RemoveListElement
- Other: PrivateTransfer, ReadRange, TextMessage, VirtualTerminal, WriteGroup, LifeSafety, Audit
- Analog: AnalogInput, AnalogOutput, AnalogValue
- Binary: BinaryInput, BinaryOutput, BinaryValue
- Multistate: MultistateInput, MultistateOutput, MultistateValue
- Access Control: AccessDoor, AccessCredential, AccessPoint, AccessRights, AccessUser, AccessZone, CredentialDataInput
- Accumulator/Pulse: Accumulator, PulseConverter
- Averaging: AveragingObject
- Command: Command
- Device & File: Device, File, Program, NetworkPort
- Elevator: ElevatorGroup, Escalator, Lift
- Event & Alarm: EventEnrollment, AlertEnrollment, EventLog, NotificationClass, NotificationForwarder
- Group: Group, GlobalGroup, StructuredView
- Life Safety: LifeSafetyPoint, LifeSafetyZone
- Lighting: LightingOutput, BinaryLightingOutput, Channel
- Load Control: LoadControl, StagingObject
- Loop: LoopObject
- Schedule & Timer: Calendar, Schedule, Timer
- Trend: TrendLog, TrendLogMultiple
- Audit: AuditLog, AuditReporter
- Value Types (12): BitStringValue, CharacterStringValue, DateValue, DatePatternValue, DateTimeValue, DateTimePatternValue, IntegerValue, LargeAnalogValue, OctetStringValue, PositiveIntegerValue, TimeValue, TimePatternValue
- Async client built on Tokio with Transaction State Machine (TSM)
- 31 async service methods covering all standard BACnet operations
- APDU segmentation receive with configurable window size
- Automatic device discovery via WhoIs with device cache
- Invoke ID management with retry logic (configurable retries and timeout)
- Support for BIP, BIP6, and SC transports via generic
TransportPorttrait - Builder pattern:
bip_builder(),sc_builder(),generic_builder()
- Async server with automatic APDU dispatch for 17 service handlers
- 62 object type factories via
add_*methods (DeviceObject auto-created at start) - Change of Value (COV) subscription engine with confirmed and unconfirmed notifications
- COV-in-flight semaphore (max 255 concurrent confirmed notifications)
- Intrinsic reporting engine with 5 algorithms: ChangeOfState, ChangeOfBitstring, ChangeOfValue, FloatingLimit, CommandFailure
- Event enrollment with algorithmic evaluation
- Fault detection and reliability evaluation (Clause 12)
- Schedule execution engine with weekly schedules and exception schedules
- Automatic trend logging with configurable polling intervals
- DeviceCommunicationControl state machine (Enable/Disable/DisableInitiation)
- PICS (Protocol Implementation Conformance Statement) generation per Annex A
- Segmentation receiver pool capped at 128 (DoS prevention)
- Network layer routing with RouterTable and DNET/DADR lookup
- Multi-port BACnetRouter for inter-network routing
- PriorityChannel for APDU queuing (critical/urgent/normal/background)
- Hop-count management and loop prevention
- Full async API via
pyo3-async-runtimeswith Tokio backend BACnetClientclass with 42 async methods mirroring the Rust client APIBACnetServerclass with 61add_*methods for all object types plus 6 runtime methodsPyScHubclass for BACnet/SC hub management- 11 enum types with named constants via
py_bacnet_enum!macro - COV async iterator (
async for event in client.cov_notifications()) - Support for BIP, IPv6, and SC transports with TLS/mTLS configuration
- Published to PyPI as
rusty-bacnet(Python ≥ 3.11)
- 1,682 tests across all crates
- CI pipeline on Linux, macOS, and Windows via GitHub Actions
- Clippy with
-Dwarnings(zero warnings policy) cargo-denyfor license/advisory/source auditing- Integration tests for client-server round trips on localhost
- 9 Criterion benchmark suites: BIP latency/throughput, BIP6 latency/throughput, SC latency/throughput, SC mTLS latency/throughput, encoding performance
- 4 Python mixed-mode benchmarks: Python↔Rust client/server cross-language performance
- Docker Compose environment for isolated benchmark runs
- 3 Rust examples: BIP client/server, COV subscriptions, multi-object server
- 5 Python examples: BIP client/server, IPv6 multicast, SC secure connect, COV subscriptions, device management
- Docker Compose example for containerized deployment