Skip to content

Commit 4bd7715

Browse files
committed
Version 1.5.2 - See changelog for updates.
1 parent a7c6ebd commit 4bd7715

22 files changed

+1548
-120
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ debug.log
267267
# Project-specific
268268
# Add any project-specific files or directories you want to ignore
269269
sc_test_certs/
270+
.sc-bench-certs/
271+
.sc-mixed-certs/
270272
config/local_config.json
271273
logs/
272274
temp/

CHANGELOG.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,67 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.5.2] - 2026-02-16
9+
10+
### Fixed
11+
12+
- **Docker entrypoint `sys.path`** — Added project root to `sys.path` so
13+
`docker.lib` imports work when the entrypoint is run as a script.
14+
- **SC PKI `generate_test_pki()`** — Clear directory contents instead of
15+
`shutil.rmtree()` on Docker volume mount points (which cannot be removed).
16+
- **SC certificate SANs** — Use specific `IPv4Address` entries instead of
17+
`IPv4Network` for Docker bridge IPs in certificate Subject Alternative Names.
18+
`IPv4Network` does not work for SSL hostname verification.
19+
20+
### Added
21+
22+
- **Mixed-environment SC profiling**`bench_sc.py` supports `--mode hub` and
23+
`--mode client` for split Docker/local benchmarks, enabling isolated
24+
pyinstrument profiling of hub-side or client-side TLS overhead.
25+
`--generate-certs DIR` creates shared TLS certificates with broad SANs
26+
(localhost, host.docker.internal, Docker bridge IPs). New Docker Compose
27+
profiles `sc-bench-hub` and `sc-bench-client` and Makefile targets
28+
`bench-sc-profile-client` and `bench-sc-profile-hub` orchestrate the
29+
mixed-environment runs.
30+
- **Docker scenario 14: Mixed BIP↔IPv6 routing** — A BACnet/IP client on
31+
network 1 communicates with a BACnet/IPv6 server on network 2 through a
32+
dual-stack `NetworkRouter`. Tests read, write, RPM, WPM, and object-list
33+
operations through the cross-transport router (6 tests).
34+
`make docker-test-mixed-bip-ipv6`.
35+
- **Docker scenario 15: Mixed BIP↔SC routing** — A BACnet/IP client sends
36+
NPDUs through a BIP↔SC `NetworkRouter` to SC echo nodes connected via an
37+
SC hub with mutual TLS 1.3 on network 2. SC echo nodes parse incoming
38+
NPDUs and swap SNET/SADR→DNET/DADR headers for proper routed responses.
39+
TLS certificates are generated locally and bind-mounted into containers
40+
(4 tests). `make docker-test-mixed-bip-sc`.
41+
- **Entrypoint roles: `router-bip-sc`, `sc-npdu-echo`** — New Docker
42+
entrypoint roles for BIP↔SC gateway routing and NPDU-level SC echo with
43+
proper routing header manipulation.
44+
45+
### Changed
46+
47+
- **Docker images tagged with version** — All services share a version-tagged
48+
image (`bac-py:<version>`) for reproducible builds.
49+
- **`docker-build` uses `docker build` directly** — Replaced `docker compose
50+
build` (which built nothing since all services have profiles) with a direct
51+
`docker build` command. `docker-clean` now removes all `bac-py:*` images.
52+
53+
- **Performance: APDU dispatch optimization** — Replaced `match`/`case` on
54+
`PduType` enum with direct `isinstance` checks in `_on_apdu_received()`,
55+
eliminating a redundant `PduType` extraction from the raw byte after
56+
`decode_apdu()` already determines the type. BIP throughput improved ~4%,
57+
Router throughput improved ~36% (cumulative with loop caching).
58+
- **Performance: Event loop caching** — Cache the running `asyncio` event loop
59+
in `BACnetApplication.start()` and use `loop.create_task()` instead of
60+
`asyncio.create_task()` in `_spawn_task()`, skipping the `get_running_loop()`
61+
lookup on every request dispatch.
62+
- **Performance: SC WebSocket pending events deque** — Changed
63+
`SCWebSocket._pending_events` from `list` with O(n) `pop(0)` to
64+
`collections.deque` with O(1) `popleft()` and built-in `maxlen=64` cap.
65+
- **Performance: SC hub payload skip** — Hub connections now decode BVLC-SC
66+
messages with `skip_payload=True`, avoiding a `bytes()` copy of the NPDU
67+
payload that the hub never inspects (it forwards raw bytes directly).
68+
869
## [1.5.1] - 2026-02-15
970

1071
### Added

Makefile

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
bench-router bench-router-json bench-router-profile \
44
bench-bbmd bench-bbmd-json bench-bbmd-profile \
55
bench-sc bench-sc-json bench-sc-profile \
6+
bench-sc-profile-client bench-sc-profile-hub \
67
docker-build docker-test docker-stress docker-test-client docker-test-bbmd \
78
docker-test-router docker-test-device-mgmt docker-test-cov-advanced \
89
docker-test-events docker-test-sc docker-test-sc-stress docker-sc-stress \
910
docker-test-router-stress docker-router-stress \
1011
docker-test-bbmd-stress docker-bbmd-stress \
1112
docker-test-ipv6 \
13+
docker-test-mixed-bip-ipv6 docker-test-mixed-bip-sc \
1214
docker-demo docker-demo-auto docker-clean
1315

1416
lint:
@@ -79,14 +81,42 @@ bench-bbmd-profile:
7981
bench-sc-profile:
8082
uv run python scripts/bench_sc.py --profile --sustain 10
8183

84+
# ---------------------------------------------------------------------------
85+
# Mixed-environment SC profiling (Docker ↔ local split)
86+
# ---------------------------------------------------------------------------
87+
88+
# Generate shared TLS certs for mixed SC benchmarks
89+
.sc-bench-certs:
90+
uv run python scripts/bench_sc.py --generate-certs .sc-bench-certs
91+
92+
# Profile client side: hub runs in Docker, echo nodes + stress client run locally
93+
bench-sc-profile-client: .sc-bench-certs docker-build
94+
SC_BENCH_CERTS=.sc-bench-certs $(COMPOSE) --profile sc-bench-hub up -d
95+
@echo "Waiting for Docker hub..." && sleep 5
96+
-uv run python scripts/bench_sc.py --mode client \
97+
--hub-uri wss://localhost:4443 --cert-dir .sc-bench-certs \
98+
--profile --sustain 15
99+
SC_BENCH_CERTS=.sc-bench-certs $(COMPOSE) --profile sc-bench-hub down
100+
101+
# Profile hub side: hub runs locally, echo nodes + stress client run in Docker
102+
bench-sc-profile-hub: .sc-bench-certs docker-build
103+
SC_BENCH_CERTS=.sc-bench-certs $(COMPOSE) --profile sc-bench-client up -d &
104+
@echo "Waiting for Docker clients..." && sleep 10
105+
uv run python scripts/bench_sc.py --mode hub --port 4443 \
106+
--cert-dir .sc-bench-certs --profile --duration 100
107+
SC_BENCH_CERTS=.sc-bench-certs $(COMPOSE) --profile sc-bench-client down
108+
82109
# ---------------------------------------------------------------------------
83110
# Docker integration tests
84111
# ---------------------------------------------------------------------------
85112

113+
BAC_PY_VERSION := $(shell uv run python -c "import bac_py; print(bac_py.__version__)")
114+
export BAC_PY_VERSION
115+
86116
COMPOSE := docker compose -f docker/docker-compose.yml
87117

88118
docker-build:
89-
$(COMPOSE) build --no-cache
119+
docker build --no-cache -t bac-py:$(BAC_PY_VERSION) -f docker/Dockerfile .
90120

91121
docker-test-client: docker-build
92122
$(COMPOSE) --profile client-server up --abort-on-container-exit --exit-code-from test-client-server
@@ -128,6 +158,18 @@ docker-test-ipv6: docker-build
128158
$(COMPOSE) --profile ipv6 up --abort-on-container-exit --exit-code-from test-ipv6
129159
$(COMPOSE) --profile ipv6 down -v
130160

161+
docker-test-mixed-bip-ipv6: docker-build
162+
$(COMPOSE) --profile mixed-bip-ipv6 up --abort-on-container-exit --exit-code-from test-mixed-bip-ipv6
163+
$(COMPOSE) --profile mixed-bip-ipv6 down -v
164+
165+
.sc-mixed-certs:
166+
uv run python -c "from docker.lib.sc_pki import generate_test_pki; from pathlib import Path; generate_test_pki(Path('.sc-mixed-certs'), names=['hub','node1','node2','router'])"
167+
168+
docker-test-mixed-bip-sc: docker-build .sc-mixed-certs
169+
SC_MIXED_CERTS=$(CURDIR)/.sc-mixed-certs $(COMPOSE) --profile mixed-bip-sc up --abort-on-container-exit --exit-code-from test-mixed-bip-sc
170+
SC_MIXED_CERTS=$(CURDIR)/.sc-mixed-certs $(COMPOSE) --profile mixed-bip-sc down -v
171+
rm -rf .sc-mixed-certs
172+
131173
docker-test: docker-build
132174
$(MAKE) docker-test-client
133175
$(MAKE) docker-test-bbmd
@@ -138,6 +180,8 @@ docker-test: docker-build
138180
$(MAKE) docker-test-events
139181
$(MAKE) docker-test-sc
140182
$(MAKE) docker-test-ipv6
183+
$(MAKE) docker-test-mixed-bip-ipv6
184+
$(MAKE) docker-test-mixed-bip-sc
141185

142186
docker-stress: docker-build
143187
$(COMPOSE) --profile stress-runner up --abort-on-container-exit --exit-code-from stress-runner
@@ -181,3 +225,8 @@ docker-clean:
181225
$(COMPOSE) --profile router-stress-runner down -v --rmi local
182226
$(COMPOSE) --profile bbmd-stress down -v --rmi local
183227
$(COMPOSE) --profile bbmd-stress-runner down -v --rmi local
228+
$(COMPOSE) --profile mixed-bip-ipv6 down -v --rmi local
229+
$(COMPOSE) --profile mixed-bip-sc down -v --rmi local
230+
rm -rf .sc-mixed-certs
231+
@echo "Removing all bac-py images..."
232+
docker images --format '{{.Repository}}:{{.Tag}}' | grep '^bac-py:' | xargs -r docker rmi 2>/dev/null || true

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async with Client(instance_number=999) as client:
4343
| **Convenience API** | String-based addressing (`"ai,1"`, `"pv"`), smart type coercion, auto-discovery |
4444
| **Serialization** | `to_dict()`/`from_dict()` on all data types; optional `orjson` backend |
4545
| **Conformance** | BIBB declarations and PICS generation per Clause 24 |
46-
| **Quality** | 6,420+ unit tests, Docker integration tests, local benchmarks, type-safe enums and frozen dataclasses throughout |
46+
| **Quality** | 6,425+ unit tests, Docker integration tests, local benchmarks, type-safe enums and frozen dataclasses throughout |
4747

4848
## Installation
4949

@@ -356,7 +356,7 @@ detailed walkthroughs.
356356
## Testing
357357

358358
```bash
359-
make test # 6,420+ unit tests
359+
make test # 6,425+ unit tests
360360
make lint # ruff check + format verification
361361
make typecheck # mypy
362362
make docs # sphinx-build
@@ -396,6 +396,8 @@ make docker-test-device-mgmt # Device management: DCC, time sync, text messa
396396
make docker-test-cov-advanced # COV: concurrent subscriptions, property-level COV
397397
make docker-test-events # Events: alarm reporting, acknowledgment, queries
398398
make docker-test-ipv6 # IPv6: BACnet/IPv6 client/server (Annex U)
399+
make docker-test-mixed-bip-ipv6 # Mixed BIP↔IPv6: cross-transport routing
400+
make docker-test-mixed-bip-sc # Mixed BIP↔SC: cross-transport routing (TLS)
399401
make docker-stress # BIP stress runner (JSON report to stdout)
400402
make docker-sc-stress # SC stress runner (JSON report to stdout)
401403
make docker-router-stress # Router stress runner (JSON report to stdout)

0 commit comments

Comments
 (0)