Skip to content

Commit 38e2f79

Browse files
committed
Removed JAVA bindings. Updated Python support.
1 parent 8a67099 commit 38e2f79

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1605
-4847
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -323,16 +323,11 @@ jobs:
323323
github-release:
324324
name: GitHub Release
325325
if: startsWith(github.ref, 'refs/tags/v')
326-
needs: [publish-crates, publish-pypi, publish-npm, publish-java, build-cli-binaries]
326+
needs: [publish-crates, publish-pypi, publish-npm, build-cli-binaries]
327327
runs-on: ubuntu-latest
328328
steps:
329329
- uses: actions/checkout@v4
330330

331-
- uses: actions/download-artifact@v4
332-
with:
333-
name: java-jar
334-
path: dist/java
335-
336331
- uses: actions/download-artifact@v4
337332
with:
338333
pattern: cli-*
@@ -343,7 +338,6 @@ jobs:
343338
with:
344339
generate_release_notes: true
345340
files: |
346-
dist/java/*.jar
347341
dist/cli/bacnet-*
348342
env:
349343
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -414,136 +408,6 @@ jobs:
414408
name: cli-${{ matrix.target }}
415409
path: ${{ matrix.binary }}
416410

417-
# ===========================================================================
418-
# Tier 2b — Java/Kotlin release
419-
# ===========================================================================
420-
421-
build-java-native:
422-
name: Java Native (${{ matrix.os }} ${{ matrix.target }})
423-
if: startsWith(github.ref, 'refs/tags/v')
424-
needs: validate
425-
runs-on: ${{ matrix.os }}
426-
strategy:
427-
fail-fast: false
428-
matrix:
429-
include:
430-
- os: ubuntu-latest
431-
target: x86_64-unknown-linux-gnu
432-
jna_dir: linux-x86-64
433-
lib_name: libbacnet_java.so
434-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls,bacnet-transport/serial,bacnet-transport/ethernet"
435-
- os: ubuntu-24.04-arm
436-
target: aarch64-unknown-linux-gnu
437-
jna_dir: linux-aarch64
438-
lib_name: libbacnet_java.so
439-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls,bacnet-transport/serial,bacnet-transport/ethernet"
440-
- os: macos-latest
441-
target: x86_64-apple-darwin
442-
jna_dir: darwin-x86-64
443-
lib_name: libbacnet_java.dylib
444-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls"
445-
- os: macos-latest
446-
target: aarch64-apple-darwin
447-
jna_dir: darwin-aarch64
448-
lib_name: libbacnet_java.dylib
449-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls"
450-
- os: windows-latest
451-
target: x86_64-pc-windows-msvc
452-
jna_dir: win32-x86-64
453-
lib_name: bacnet_java.dll
454-
features: "--features bacnet-transport/ipv6,bacnet-transport/sc-tls"
455-
steps:
456-
- uses: actions/checkout@v4
457-
- uses: dtolnay/rust-toolchain@master
458-
with:
459-
toolchain: "1.93"
460-
targets: ${{ matrix.target }}
461-
- uses: Swatinem/rust-cache@v2
462-
with:
463-
key: java-${{ matrix.target }}
464-
465-
- name: Install Linux aarch64 dependencies
466-
if: matrix.target == 'aarch64-unknown-linux-gnu' && runner.arch != 'ARM64'
467-
run: |
468-
sudo apt-get update
469-
sudo apt-get install -y gcc-aarch64-linux-gnu
470-
471-
- name: Build native library
472-
run: cargo build -p bacnet-java --release --target ${{ matrix.target }} ${{ matrix.features }}
473-
474-
- name: Upload native library
475-
uses: actions/upload-artifact@v4
476-
with:
477-
name: java-native-${{ matrix.jna_dir }}
478-
path: target/${{ matrix.target }}/release/${{ matrix.lib_name }}
479-
480-
publish-java:
481-
name: Publish Java/Kotlin JAR
482-
if: startsWith(github.ref, 'refs/tags/v')
483-
needs: [build-java-native]
484-
runs-on: ubuntu-latest
485-
permissions:
486-
packages: write
487-
contents: read
488-
steps:
489-
- uses: actions/checkout@v4
490-
491-
- uses: dtolnay/rust-toolchain@master
492-
with:
493-
toolchain: "1.93"
494-
- uses: Swatinem/rust-cache@v2
495-
496-
- uses: actions/setup-java@v4
497-
with:
498-
distribution: temurin
499-
java-version: "21"
500-
501-
- name: Download all native libraries
502-
uses: actions/download-artifact@v4
503-
with:
504-
pattern: java-native-*
505-
path: native-libs
506-
507-
- name: Build Linux x86_64 cdylib for binding generation
508-
run: cargo build -p bacnet-java --release
509-
510-
- name: Build uniffi-bindgen
511-
run: cargo build -p uniffi-bindgen
512-
513-
- name: Generate Kotlin bindings
514-
run: |
515-
cargo run -p uniffi-bindgen -- generate \
516-
--library target/release/libbacnet_java.so \
517-
--language kotlin \
518-
--no-format \
519-
--out-dir java/src/main/kotlin
520-
521-
- name: Assemble native libraries into JNA layout
522-
run: |
523-
for dir in native-libs/java-native-*/; do
524-
JNA_DIR=$(basename "$dir" | sed 's/java-native-//')
525-
mkdir -p "java/src/main/resources/$JNA_DIR"
526-
cp "$dir"/* "java/src/main/resources/$JNA_DIR/"
527-
done
528-
find java/src/main/resources -type f | sort
529-
530-
- name: Build JAR
531-
working-directory: java
532-
run: ./gradlew build
533-
534-
- name: Publish to GitHub Packages
535-
working-directory: java
536-
run: ./gradlew publish
537-
env:
538-
GITHUB_ACTOR: ${{ github.actor }}
539-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
540-
541-
- name: Upload JAR artifact
542-
uses: actions/upload-artifact@v4
543-
with:
544-
name: java-jar
545-
path: java/build/libs/*.jar
546-
547411
# ===========================================================================
548412
# Docker Hub (placeholder for 1.0)
549413
# ===========================================================================

.gitignore

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,6 @@ Thumbs.db
4848
*.swo
4949
*~
5050

51-
# Java/Gradle
52-
java/.gradle/
53-
java/build/
54-
java/benchmarks/.gradle/
55-
java/benchmarks/build/
56-
java/src/main/kotlin/uniffi/
57-
java/src/main/resources/darwin-*/
58-
java/src/main/resources/linux-*/
59-
java/src/main/resources/win32-*/
60-
6151
# JavaScript/WASM
6252
node_modules/
6353
pkg/

Benchmarks.md

Lines changed: 3 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Rusty BACnet — Benchmarks & Stress Test Results
22

3-
> Last full run: 2026-03-05 | Platform: macOS (Apple Silicon) | Rust 1.93 | JDK 21.0.10 | Release mode
3+
> Last full run: 2026-03-05 | Platform: macOS (Apple Silicon) | Rust 1.93 | Release mode
44
>
55
> TLS provider: aws-lc-rs | All tests ran on localhost with zero errors unless noted.
66
>
@@ -228,14 +228,6 @@ Peak RSS: 9.2 MB. Scan time scales linearly.
228228
SC mTLS adds negligible overhead vs server-auth-only SC — the TLS handshake dominates, not per-message client cert verification.
229229
Python concurrent throughput is competitive with Rust single-threaded due to tokio's multi-threaded runtime handling the actual I/O.
230230

231-
### Kotlin/JVM (UniFFI/JNA, coroutines)
232-
233-
| Transport | RP Latency | Sequential Throughput | Overhead vs Rust |
234-
|---|---|---|---|
235-
| **BIP (Kt→Rust)** | ~74 µs | 14.0 K/s | +2.7× latency |
236-
237-
Kotlin/JNA overhead (~46 µs) is ~40% lower than Python/PyO3 (~80 µs) per async call.
238-
239231
---
240232

241233
## 4. Docker Cross-Network Tests
@@ -441,81 +433,12 @@ comparable to pure Rust's single-threaded 36K/s.
441433

442434
---
443435

444-
## 7. Kotlin/JVM ↔ Rust Benchmarks (UniFFI/JNA)
445-
446-
> JDK 21.0.10 (OpenJDK, Apple Silicon) | UniFFI 0.29 + JNA 5.15 | JMH 1.37 | kotlinx-coroutines 1.9.0
447-
>
448-
> All tests use the `bacnet-java` UniFFI bindings on localhost over BIP (UDP/IPv4).
449-
> Server hosts 15 mixed objects (analog-input/output, binary-value, multistate-input).
450-
451-
### 7.1 BACnet/IP — Kotlin Client → Rust Server
452-
453-
| Operation | Mean | ± Error |
454-
|---|---|---|
455-
| ReadProperty | **73.9 µs** | ± 1.1 µs |
456-
| WriteProperty | **80.6 µs** | ± 5.7 µs |
457-
| RPM (3×2 props) | **78.1 µs** | ± 0.7 µs |
458-
| COV Sub/Unsub | **132.8 µs** | ± 2.8 µs |
459-
| WhoIs | **30.7 µs** | ± 3.5 µs |
460-
461-
Sequential throughput: **~14,000 ops/s** (ReadProperty)
462-
463-
### 7.2 Concurrency Scaling
464-
465-
| Coroutines | Throughput | Per-coroutine |
466-
|---|---|---|
467-
| 1 | **13,258 ops/s** | 13,258 /s |
468-
| 5 | 4,534 ops/s | 907 /s |
469-
| 10 | 2,636 ops/s | 264 /s |
470-
| 25 | 1,116 ops/s | 45 /s |
471-
472-
Note: JMH measures one benchmark iteration at a time. Each iteration launches N coroutines
473-
that each do a full ReadProperty round-trip. The throughput decrease at higher concurrency
474-
reflects the cost of N sequential round-trips per iteration, not a server bottleneck.
475-
476-
### 7.3 JNA/FFI Overhead
477-
478-
| Operation | Mean | ± Error |
479-
|---|---|---|
480-
| ObjectIdentifier (create) | **10.9 µs** | ± 9.7 µs |
481-
| ObjectIdentifier (display) | **14.9 µs** | ± 5.7 µs |
482-
| PropertyValue (Real) | **3.2 ns** | ± 0.6 ns |
483-
| PropertyValue (String) | **3.1 ns** | ± 0.05 ns |
484-
| PropertyValue (Unsigned) | **4.1 ns** | ± 0.04 ns |
485-
486-
Simple Kotlin enum construction (PropertyValue variants) is **~3 ns** — pure JVM allocation.
487-
ObjectIdentifier creation crosses the JNA FFI boundary at **~11 µs** per call (higher variance
488-
due to JNA native library loading and GC interaction).
489-
490-
### 7.4 Object Creation
491-
492-
| Operation | Mean | ± Error |
493-
|---|---|---|
494-
| Add AnalogInput | **39.0 µs** | ± 13.1 µs |
495-
| Add 5 mixed objects | **190.4 µs** | ± 72.4 µs |
496-
497-
Server object creation includes FFI crossing + Rust object construction + database insertion.
498-
499-
### 7.5 Kotlin API Overhead Analysis
500-
501-
| Transport | Rust Latency | Kotlin Latency | Overhead |
502-
|---|---|---|---|
503-
| BIP ReadProperty | 27.5 µs | ~74 µs | ~46 µs (+2.7×) |
504-
| BIP WriteProperty | 28.7 µs | ~81 µs | ~52 µs (+2.8×) |
505-
| BIP RPM (3×2) | 32.0 µs | ~78 µs | ~46 µs (+2.4×) |
506-
507-
Kotlin/JNA overhead is **~46–52 µs** per async round-trip, lower than Python's ~80 µs.
508-
The overhead comes from: UniFFI async dispatch (~10 µs), JNA FFI boundary (~11 µs per crossing),
509-
and Kotlin coroutine suspension/resumption (~25 µs).
510-
511-
---
512-
513-
## 8. Key Takeaways
436+
## 7. Key Takeaways
514437

515438
- **Encoding is fast**: Full RP encode/decode stack in ~131 ns (CPU-bound, no allocation hot paths thanks to `Bytes` zero-copy)
516439
- **BIP throughput scales linearly**: 40K/s single-client → 161K/s at 50 clients with sub-millisecond p99
517440
- **Concurrent dispatch unlocks RwLock parallelism**: Server now spawns per-request tasks — multiple ReadProperty requests run truly concurrently via `db.read()`. Quick benchmarks show ~44% read throughput improvement (full run pending)
518-
- **Multi-device batch API**: Client `read_property_from_devices()` / `read_property_multiple_from_devices()` / `write_property_to_devices()` fan out to N devices concurrently with configurable `max_concurrent` (default 32). Available in Rust, Python, and Java/Kotlin
441+
- **Multi-device batch API**: Client `read_property_from_devices()` / `read_property_multiple_from_devices()` / `write_property_to_devices()` fan out to N devices concurrently with configurable `max_concurrent` (default 32). Available in Rust and Python
519442
- **Object count doesn't matter**: 100 → 5,000 objects shows zero latency degradation (RwLock contention minimal)
520443
- **COV is reliable**: 100% notification delivery at 25 concurrent subscriptions
521444
- **SC overhead is ~2.5×**: TLS WebSocket adds ~40 µs per operation vs raw UDP — acceptable for secure deployments
@@ -525,8 +448,6 @@ and Kotlin coroutine suspension/resumption (~25 µs).
525448
- **Musl/Alpine parity**: Docker (static musl) matches native performance — no penalty for containerized deployment
526449
- **Python API is production-ready**: ~80 µs PyO3 overhead per call; 36K concurrent ops/s from Python matches pure Rust throughput
527450
- **SC from Python works**: ScHub + SC client/server all work via PyO3; 29K ops/s at 25 concurrent clients
528-
- **Kotlin/JNA is faster than Python**: ~46 µs UniFFI overhead per async call vs Python's ~80 µs; 14K sequential ops/s
529-
- **JNA primitive overhead is negligible**: PropertyValue enum construction is ~3 ns (pure JVM); ObjectIdentifier FFI crossing ~11 µs
530451

531452
---
532453

@@ -566,10 +487,4 @@ uv run pytest bench_py_client_rust_server.py -v # BIP: Py client → Rust serv
566487
uv run pytest bench_rust_client_py_server.py -v # BIP: Rust client → Py server
567488
uv run pytest bench_py_py.py -v # BIP: Py ↔ Py
568489
uv run pytest bench_sc.py -v # SC: Py client → Rust server via ScHub
569-
570-
# Kotlin/JVM JMH benchmarks (requires JDK 21+)
571-
cd java
572-
./build-local.sh --release # Build native lib + Kotlin bindings + JAR
573-
./gradlew :benchmarks:jmh # Full benchmark suite (~10 min)
574-
# Results: java/benchmarks/build/reports/jmh/results.json
575490
```

CHANGELOG.md

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,69 @@ Deep-dive review of all five transport implementations (BIP, BIPv6, BACnet/SC, E
113113
- **Added** named `NPDU_CHANNEL_CAPACITY` constants in all transports (256 for BIP/BIP6/Ethernet/Loopback, 64 for SC/MS/TP) with documented rationale
114114
- **Changed** `bip6` module feature-gated behind `ipv6` feature flag — consistent with `ethernet` and `sc-tls` gating; propagated to bacnet-client, bacnet-java, bacnet-btl, bacnet-cli, benchmarks
115115

116+
### Spec Compliance — Stack-Wide (ASHRAE 135-2020 Clauses 5, 6, 12, 13, 15, 16, 20)
117+
118+
Deep-dive review of encoding, types, services, objects, client, server, and network layers identified 43 spec compliance issues. All critical/high/medium fixed.
119+
120+
#### Encoding & APDU (Clause 20)
121+
- **Fixed** SegmentAck window size not clamped to 1-127 range on decode — now clamps with warning log per Clause 20.1.6
122+
- **Fixed** reserved max_apdu values silently accepted — now logs warning for non-standard values
123+
124+
#### Types & Enums (Clause 21)
125+
- **Fixed** LifeSafetyOperation enum ordering — reset=4, reset-alarm=5, reset-fault=6, unsilence=7 per Table 12-54
126+
- **Added** LifeSafetyMode OEO values (15-19) per 135-2020 addendum
127+
- **Added** DaysOfWeek bitflags type for schedule encoding
128+
- **Added** 11 new BACnetPropertyStates variants (UnsignedValue, DoorAlarmState, Action, DoorSecuredStatus, DoorStatus, DoorValue, TimerState, TimerTransition, LiftCarDirection, LiftCarDoorCommand)
129+
130+
#### Services (Clauses 13-16)
131+
- **Fixed** TextMessage tags — messagePriority and message use context tags [2] and [3] (were [3] and [4])
132+
- **Fixed** ReinitializeDevice password validation — SIZE(1..20) per Clause 16.4.1.1.5
133+
- **Added** `message_text: Option<String>` field to EventNotificationRequest with encode/decode per Clause 13.8.1
134+
- **Added** `RecipientProcess` struct and `enrollment_filter` field to GetEnrollmentSummaryRequest
135+
136+
#### Objects (Clause 12)
137+
- **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`
138+
- **Added** `compute_status_flags()` helper function for consistent StatusFlags computation across object types
139+
- **Added** ValueSourceTracking fields (VALUE_SOURCE, LAST_COMMAND_TIME) to AV, BO, BV, MSO, MSV
140+
- **Added** `set_overridden()` default method on BACnetObject trait
141+
142+
#### Client (Clause 5.4)
143+
- **Fixed** per-window SegmentAck — tracks window position for correct sequence acknowledgment
144+
- **Fixed** duplicate segment handling in segmented response reassembly
145+
- **Fixed** negative SegmentAck uses `wrapping_sub(1)` for correct sequence arithmetic
146+
- **Added** Abort on unsupported segmented response when `segmented_response_accepted` is false
147+
- **Added** `segmented_response_accepted` parameter threading through dispatch_apdu/handle_segmented_complex_ack
148+
- **Added** device table auto-purge every 5 minutes for stale entries
149+
150+
#### Server
151+
- **Fixed** COV notification `ack_required` flag — `notify_type == NotifyType::ALARM` (was `!= ACK_NOTIFICATION`)
152+
- **Fixed** DCC DISABLE now accepted — all 3 EnableDisable values work correctly per 135-2020
153+
- **Fixed** COVProperty cancel now calls `unsubscribe_property()` instead of `unsubscribe()`
154+
- **Fixed** RPM handler resolves device wildcard via `resolve_device_wildcard()`
155+
- **Fixed** GetEnrollmentSummary priority lookup reads from notification class object (was hardcoded 0)
156+
- **Fixed** intrinsic reporting silently non-functional — EVENT_ENABLE stored as BitString but read via `read_unsigned()`; added `read_event_enable()` helper handling both types
157+
- **Fixed** schedule tick passes UTC offset parameter for correct time computation
158+
- **Fixed** EventNotificationRequest now includes `message_text: None` field
159+
- **Added** `days_to_date()` helper for full datetime in trend log records
160+
161+
#### Network (Clause 6)
162+
- **Fixed** remote broadcast self-delivery — router now delivers broadcast to local network layer
163+
- **Fixed** `is_network_message` passthrough in routing (was hardcoded false)
164+
- **Fixed** proprietary network messages (type >= 0x80) with DNET now forwarded correctly
165+
- **Fixed** Init-Routing-Table-Ack uses actual port_index (was hardcoded)
166+
167+
### Python Bindings Improvements
168+
169+
- **Rewritten** `.pyi` type stubs from scratch (826 → 1598 lines) — all 47 client methods, 62+ server methods, correct exception names, CovNotification class, PropertyValue constructors, all 65 ObjectType constants
170+
- **Added** `time_synchronization()` and `utc_time_synchronization()` methods
171+
- **Added** `who_is_directed()` for unicast WhoIs
172+
- **Added** auto-routing methods: `read_property_from_device()`, `read_property_multiple_from_device()`, `write_property_to_device()`, `write_property_multiple_to_device()`
173+
- **Added** `add_device()` for manual device table population
174+
- **Added** `discover(timeout_ms)` convenience method — combines WhoIs + sleep + discovered_devices
175+
- **Added** `PropertyValue.date()`, `.time()`, `.bit_string()`, `.list()` static constructors
176+
- **Added** structured error attributes — `BacnetProtocolError.error_class`/`.error_code`, `BacnetRejectError.reason`, `BacnetAbortError.reason`
177+
- **Added** `dcc_password` and `reinit_password` parameters to `BACnetServer` constructor
178+
116179
### Added
117180
- **New crate: `bacnet-gateway`** — HTTP REST API and MCP (Model Context Protocol) server for BACnet networks
118181
- REST API at `/api/v1/` with endpoints for device discovery, property read/write, local object CRUD, and health check
@@ -125,14 +188,15 @@ Deep-dive review of all five transport implementations (BIP, BIPv6, BACnet/SC, E
125188
- **Client batch operations**`read_property_from_devices()`, `read_property_multiple_from_devices()`, `write_property_to_devices()` with `buffer_unordered(max_concurrent)` for concurrent multi-device I/O
126189
- **Client auto-routing**`resolve_device()` helper + `_from_device` variants for RP, RPM, WP, WPM
127190
- **Server concurrent dispatch** — spawns per-request tasks for ConfirmedRequest/UnconfirmedRequest, enabling concurrent `db.read()` from multiple clients
128-
- **Python bindings** — batch methods (`read_property_from_devices`, etc.) and COV async iterator improvements
129-
- **Java/Kotlin bindings** — batch methods and `CovNotificationStream` improvements
130191
- **Architecture documentation**`docs/architecture.md`, expanded `docs/rust-api.md`, `docs/gateway.md`, `docs/btl.md`, `docs/wasm-api.md`
131192

132193
### Changed
133194
- **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
134195
- **Security advisories resolved** — aws-lc-sys X.509 name constraints bypass, CRL distribution point logic errors; rustls-webpki CRL scope check
135196

197+
### Removed
198+
- **Java/Kotlin bindings** — removed `bacnet-java` crate, `uniffi-bindgen` crate, `java/` Gradle project, `examples/kotlin/`, and all associated CI jobs (no user base; maintenance burden)
199+
136200
## [0.7.2]
137201

138202
### Added

0 commit comments

Comments
 (0)