# Code Review — Driver.Galaxy.Browser | 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 ### 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 2–6 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 2–6) would have been caught immediately had these tests existed. **Recommendation:** Add `[Fact]` tests for each of the seven known codes (0–6) 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 0–6 plus an out-of-range value.