NamespaceMap relocation deferred: the Browser references only Contracts (not the runtime driver), so the recommended move would break its compile; the only clean relocation needs a new shared-helper project — disproportionate to a Low finding. All 51 modules now have zero Open/In-Progress findings.
7.0 KiB
Code Review — Driver.OpcUaClient.Contracts
| Field | Value |
|---|---|
| Module | src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts |
| Reviewer | Claude Code |
| Review date | 2026-06-19 |
| Commit reviewed | a19b0f86 |
| Status | Reviewed |
| Open findings | 0 |
Checklist coverage
| # | Category | Result |
|---|---|---|
| 1 | Correctness & logic bugs | Driver.OpcUaClient.Contracts-001 |
| 2 | OtOpcUa conventions | Driver.OpcUaClient.Contracts-002 |
| 3 | Concurrency & thread safety | No issues found |
| 4 | Error handling & resilience | No issues found |
| 5 | Security | No issues found |
| 6 | Performance & resource management | No issues found |
| 7 | Design-document adherence | Driver.OpcUaClient.Contracts-002 |
| 8 | Code organization & conventions | Driver.OpcUaClient.Contracts-003 |
| 9 | Testing coverage | No test project — no issues to record |
| 10 | Documentation & comments | No issues found |
Findings
Driver.OpcUaClient.Contracts-001
| Field | Value |
|---|---|
| Severity | Medium |
| Category | Correctness & logic bugs |
| Location | src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts/NamespaceMap.cs:118-134 |
| Status | Resolved |
Description: TryResolve accepts currentSession as a non-nullable ISession parameter but contains a runtime null guard (if (currentSession is null || …) return false). The non-nullable annotation tells callers that null is invalid, yet the implementation silently returns false on null — masking the bug that prompted the guard. More practically: on the driver's reconnect path _namespaceMap is rebuilt under _probeLock, but TryResolve is called outside that lock (at the read/write call-site). A session handed to TryResolve concurrently with a session swap could be non-null at the call site but torn-down inside the call; the silent false return gives the caller no indication that resolution failed due to a racing session swap versus a genuinely unknown NodeId. The out NodeId nodeId parameter contract also does not document that callers must use NodeId.IsNull(nodeId) (OPC UA SDK idiom) rather than nodeId == null (C# null) to test the out-value — no current caller makes this mistake, but the distinction is non-obvious.
Recommendation: Change the parameter to ISession? currentSession (nullable) so the annotation matches reality and the compiler guides callers. Add XML-doc on the out nodeId parameter specifying NodeId.IsNull(nodeId) is the correct test.
Resolution: Resolved 2026-06-19, verified by build (no test project). Changed ISession currentSession to ISession? currentSession so the nullable annotation matches the runtime guard. Expanded XML doc on currentSession to explain the defensive-null contract and on nodeId to specify NodeId.IsNull(nodeId) as the correct test. Browser and runtime driver both build clean (0 errors, 0 warnings) with TreatWarningsAsErrors=true.
Driver.OpcUaClient.Contracts-002
| Field | Value |
|---|---|
| Severity | Low |
| Category | OtOpcUa conventions / Design-document adherence |
| Location | src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts.csproj:9 |
| Status | Deferred |
Description: The Contracts project carries a direct PackageReference to OPCFoundation.NetStandard.Opc.Ua.Client solely to support NamespaceMap. This makes the Contracts project not a lightweight DTO/enum assembly: every consumer (AdminUI serializers, future test helpers, config-only tooling) that references Contracts for OpcUaClientDriverOptions and its enums also transitively receives the full OPC UA client SDK. Both OpcUaClientDriver and OpcUaClientDriverBrowser already reference the SDK directly and could host NamespaceMap without any new dependency. Moving NamespaceMap to the runtime driver (or to a dedicated shared helper project referenced by both driver and browser) would restore the Contracts project to its intended SDK-free role.
Recommendation: Move NamespaceMap out of Contracts into ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient (or an internal shared helper). Remove the OPCFoundation.NetStandard.Opc.Ua.Client reference from the Contracts .csproj. NamespaceMap is not part of the public wire contract; it is an implementation detail of the driver's namespace-stability mechanism.
Resolution: Deferred 2026-06-20. Investigated the relocation during the open-findings resolution pass. NamespaceMap is consumed by the runtime driver (Driver.OpcUaClient) AND the Browser (Driver.OpcUaClient.Browser), but the Browser references only Driver.OpcUaClient.Contracts — it has no ProjectReference to the runtime driver. So the recommended "move it into Driver.OpcUaClient" would break the Browser's compile. The only relocation that keeps both consumers building without the Browser taking a runtime-driver dependency is a brand-new dedicated shared-helper project referenced by both — a structural change (new .csproj, solution-file edit, two ProjectReference rewrites) disproportionate to a Low architectural-cleanliness finding whose only cost is the OPC UA SDK being transitively visible to Contracts consumers (AdminUI already references the SDK directly via the driver anyway). Left Deferred until an OpcUaClient project-layout refactor is undertaken for an independent reason; revisit then.
Driver.OpcUaClient.Contracts-003
| Field | Value |
|---|---|
| Severity | Low |
| Category | Code organization & conventions |
| Location | src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts/OpcUaClientDriverOptions.cs:131-138, :174-176 |
| Status | Resolved |
Description: Two inconsistency gaps in the options class:
-
[Range(1, 60)]is applied only toProbeTimeoutSeconds.MaxDiscoveredNodesandMaxBrowseDepthhave meaningful minimum bounds (zero or negative values produce silent misbehaviour:MaxDiscoveredNodes = 0halts all discovery,MaxBrowseDepth = 0skips all browsing). No[Range]guards exist for these knobs. -
The
[Display(Name = …, Description = …, GroupName = "Diagnostics")]attribute onProbeTimeoutSecondsis unused dead metadata: the AdminUI page binds the field directly viaInputNumber @bind-Valueand does not read[Display]attributes. No UI scaffold, reflection-based form generator, or API documentation tool in the codebase consumes it. Having a[Display]on one property of 18 properties creates a false impression that more of the class is attributed.
Recommendation: Add [Range(1, int.MaxValue)] to MaxDiscoveredNodes and MaxBrowseDepth. Remove the [Display] from ProbeTimeoutSeconds (retaining [Range(1, 60)] which is enforced by the AdminUI's DataAnnotationsValidator).
Resolution: Resolved 2026-06-19, verified by build (no test project). Added [Range(1, int.MaxValue)] to MaxDiscoveredNodes and MaxBrowseDepth. Removed the dead [Display(…)] attribute from ProbeTimeoutSeconds; [Range(1, 60)] retained. All three projects (Contracts, Driver, Browser) build clean with TreatWarningsAsErrors=true.