Files
lmxopcua/code-reviews/Driver.Galaxy.Browser/findings.md
T
Joseph Doherty 960d76ffcb review(Driver.Galaxy.Browser): fix mis-shifted MapSecurityClass codes (High)
Review at HEAD 7286d320. Driver.Galaxy.Browser-001 (High): MapSecurityClass codes 2-6 were
all shifted vs the runtime SecurityClassification enum (wrong security labels in the picker)
-> corrected all 7 arms + tests. -002: DisposeAsync swallows concurrent ObjectDisposedException.
-003 (ResolveApiKey dup) deferred to Contracts.
2026-06-19 10:52:23 -04:00

163 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Code Review — Driver.Galaxy.Browser
<!-- Template for a per-module findings file. Copy to code-reviews/<Module>/findings.md.
See ../../REVIEW-PROCESS.md for the full process. The base README.md is generated
from these files by regen-readme.py — do not edit README.md by hand. -->
| Field | Value |
|---|---|
| Module | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser` |
| Reviewer | Claude Code |
| Review date | 2026-06-19 |
| Commit reviewed | `7286d320` |
| Status | Reviewed |
| Open findings | 1 |
## Checklist coverage
A comprehensive review completes every category, recording "No issues found" where
a category produced nothing rather than leaving it blank.
| # | Category | Result |
|---|---|---|
| 1 | Correctness & logic bugs | Driver.Galaxy.Browser-001 (High): MapSecurityClass codes are shifted — mismatches the runtime SecurityMap |
| 2 | OtOpcUa conventions | No issues found |
| 3 | Concurrency & thread safety | Driver.Galaxy.Browser-002 (Medium): DisposeAsync has a TOCTOU race on _rootGate.Dispose() |
| 4 | Error handling & resilience | No issues found |
| 5 | Security | No issues found |
| 6 | Performance & resource management | No issues found |
| 7 | Design-document adherence | No issues found |
| 8 | Code organization & conventions | Driver.Galaxy.Browser-003 (Low): ResolveApiKey duplicated from GalaxyDriver with no sync mechanism |
| 9 | Testing coverage | Driver.Galaxy.Browser-004 (Low): MapSecurityClass not unit-tested; pure static method with no gateway dependency |
| 10 | Documentation & comments | No issues found |
## Findings
<!-- One ### entry per finding. IDs are <Module>-NNN, sequential within the module,
never reused. Findings are never deleted — close them by changing Status and
completing Resolution. -->
### Driver.Galaxy.Browser-001
| Field | Value |
|---|---|
| Severity | High |
| Category | Correctness & logic bugs |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser/GalaxyBrowseSession.cs:152` |
| Status | Resolved |
**Description:** `GalaxyBrowseSession.MapSecurityClass` maps Galaxy `security_classification`
integer codes incorrectly. The Browser's switch arms are:
| Code | Browser output | Runtime `SecurityMap` / `SecurityClassification` enum |
|---|---|---|
| 0 | "FreeAccess" | FreeAccess ✓ |
| 1 | "Operate" | Operate ✓ |
| 2 | "Tune" | **SecuredWrite** ✗ |
| 3 | "Configure" | **VerifiedWrite** ✗ |
| 4 | "ViewOnly" | **Tune** ✗ |
| 5 | "Unknown(5)" | **Configure** ✗ |
| 6 | "Unknown(6)" | **ViewOnly** ✗ |
Codes 26 are all wrong. The AdminUI attribute side-panel labels "SecuredWrite" attributes
as "Tune" and "VerifiedWrite" attributes as "Configure", causing operators to misread
write-protection levels when selecting Galaxy tags. A tag the operator thinks is
"Tune"-restricted is actually read-only (SecuredWrite / VerifiedWrite → ViewOnly from
the OPC UA server's perspective). The correct mapping is already implemented in the
sibling `Driver.Galaxy/Browse/SecurityMap.cs` and matches the `SecurityClassification`
enum's integer assignments exactly.
**Recommendation:** Fix `MapSecurityClass` to match `SecurityClassification` enum ordinals:
0→FreeAccess, 1→Operate, 2→SecuredWrite, 3→VerifiedWrite, 4→Tune, 5→Configure,
6→ViewOnly; unknown → `"Unknown({raw})"`. Add a unit test asserting each code.
**Resolution:** Fixed 2026-06-19. Corrected all seven code-to-label arms in
`MapSecurityClass` to match `SecurityClassification` enum ordinals. Regression tests
`MapSecurityClass_maps_all_known_codes` and
`MapSecurityClass_unknown_code_returns_Unknown_label` added.
---
### Driver.Galaxy.Browser-002
| Field | Value |
|---|---|
| Severity | Medium |
| Category | Concurrency & thread safety |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser/GalaxyBrowseSession.cs:167` |
| Status | Resolved |
**Description:** `DisposeAsync` uses a non-atomic read-then-write pattern on the
`_disposed` volatile field:
```csharp
if (_disposed) return;
_disposed = true;
_rootGate.Dispose();
```
Two concurrent callers can both observe `_disposed == false`, both set it to `true`, and
both call `_rootGate.Dispose()`. The second call throws `ObjectDisposedException` from
`SemaphoreSlim.Dispose()`, which propagates uncaught because the `try/catch` that
follows only wraps `_client.DisposeAsync()`. The `BrowseSessionReaper` (background
service) and a browser-side "Close" button can race in production.
**Recommendation:** Wrap `_rootGate.Dispose()` in a `try/catch (ObjectDisposedException)`
mirroring the existing client disposal pattern, or use an atomic int guard via
`Interlocked.Exchange`.
**Resolution:** Fixed 2026-06-19. Added `try { _rootGate.Dispose(); } catch (ObjectDisposedException) { }`
mirroring the existing pattern for `_client.DisposeAsync()`. Regression test
`DisposeAsync_concurrent_calls_do_not_throw` added.
---
### Driver.Galaxy.Browser-003
| Field | Value |
|---|---|
| Severity | Low |
| Category | Code organization & conventions |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser/GalaxyDriverBrowser.cs:149` |
| Status | Open |
**Description:** `GalaxyDriverBrowser.ResolveApiKey` is a verbatim copy of
`GalaxyDriver.ResolveApiKey`. The comment acknowledges this and explains why the Browser
project intentionally does not reference Driver.Galaxy. However, Finding
Driver.Galaxy.Browser-001 (the `MapSecurityClass` drift) demonstrates that duplicated
logic diverges. If a new secret-ref prefix (e.g. `vault:`) is added to the runtime
resolver, the Browser version will silently fall through to the cleartext-literal arm
and emit a spurious warning.
**Recommendation:** Extract `ResolveApiKey` into the `Driver.Galaxy.Contracts` project,
which both `Driver.Galaxy` and `Driver.Galaxy.Browser` already reference. This is a
one-line addition to Contracts — no migration, no public-contract break.
**Resolution:** _(deferred — requires a change in the Galaxy.Contracts project, outside
this module's boundary; tracked for a future consolidation pass)_
---
### Driver.Galaxy.Browser-004
| Field | Value |
|---|---|
| Severity | Low |
| Category | Testing coverage |
| Location | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser.Tests/GalaxyBrowseSessionTests.cs` |
| Status | Resolved |
**Description:** `GalaxyBrowseSession.MapSecurityClass` (all seven codes plus the
`Unknown(N)` fallback) had zero unit-test coverage. The existing test comment correctly
notes that RootAsync/ExpandAsync/AttributesAsync traversal is blocked by the internal
transport seam, but `MapSecurityClass` is a pure static method with no gateway dependency
— it is entirely testable in the unit suite. Finding Driver.Galaxy.Browser-001 (wrong
mapping for codes 26) would have been caught immediately had these tests existed.
**Recommendation:** Add `[Fact]` tests for each of the seven known codes (06) and the
unknown-code fallback.
**Resolution:** Fixed 2026-06-19. Tests `MapSecurityClass_maps_all_known_codes` and
`MapSecurityClass_unknown_code_returns_Unknown_label` added, covering all codes 06
plus an out-of-range value.