Cross-module fix from the review sweep. -003 (Medium): the browser's JsonOpts lacked JsonStringEnumConverter (the factory+probe both carry it), so AdminUI string-enum configs (AuthType/SecurityPolicy/SecurityMode/TargetNamespaceKind) threw on deserialize. Added the converter (accepts string AND numeric) + TDD.
6.5 KiB
Code Review — Driver.OpcUaClient.Browser
| Field | Value |
|---|---|
| Module | src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser |
| Reviewer | Claude Code |
| Review date | 2026-06-19 |
| Commit reviewed | 7286d320 |
| Status | Reviewed |
| Open findings | 0 |
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 | Finding 001 (LastUsedUtc not updated in AttributesAsync) |
| 2 | OtOpcUa conventions | No issues found |
| 3 | Concurrency & thread safety | No issues found (disposal race is accepted pattern, same as GalaxyBrowseSession; TTL reaper evicts only idle sessions) |
| 4 | Error handling & resilience | No issues found (BrowseRoot parse failure is caught and session cleaned up; continuation-point cancel-leak is cross-cutting — see 002) |
| 5 | Security | No issues found (separate PKI store correct; AutoAcceptCertificates not forwarded to browse store; no credential logging) |
| 6 | Performance & resource management | Finding 002 (continuation-point server-side leak on cancellation) |
| 7 | Design-document adherence | No issues found |
| 8 | Code organization & conventions | No issues found |
| 9 | Testing coverage | No issues found (unit tests cover pre-connect validation; live tests correctly gated on RequiresOpcPlc) |
| 10 | Documentation & comments | No issues found |
Findings
Driver.OpcUaClient.Browser-001
| Field | Value |
|---|---|
| Severity | Low |
| Category | Correctness & logic bugs |
| Location | src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientBrowseSession.cs:66 |
| Status | Resolved |
Description: AttributesAsync returns Task.FromResult(Array.Empty<AttributeInfo>()) without updating LastUsedUtc. The IBrowseSession interface contract (at src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/IBrowseSession.cs:14-15) states that LastUsedUtc is "Refreshed on RootAsync, ExpandAsync, and AttributesAsync". If the AdminUI calls AttributesAsync exclusively for 2+ minutes without an intermediate RootAsync or ExpandAsync (e.g. in a polymorphic picker context), the BrowseSessionReaper may evict an active session early.
Although the OpcUaClient browser never issues a server round-trip in AttributesAsync (it always returns empty), the timestamp must be refreshed to honour the contract and keep the session alive while the user is active.
Recommendation: Set LastUsedUtc = DateTime.UtcNow inside AttributesAsync before returning, mirroring the pattern in BrowseOneLevelAsync.
Resolution: Fixed 2026-06-19 (SHA pending commit) — added LastUsedUtc = DateTime.UtcNow to AttributesAsync body; regression test AttributesAsync_updates_LastUsedUtc added to the unit test project.
Driver.OpcUaClient.Browser-002
| Field | Value |
|---|---|
| Severity | Low |
| Category | Performance & resource management |
| Location | src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientBrowseSession.cs:112-125 |
| Status | Deferred |
Description: If the caller's CancellationToken fires during the BrowseNextAsync pagination loop, OperationCanceledException propagates out of the loop and any in-progress server-side continuation point is never released via BrowseNext(releaseContinuationPoints: true). The server holds the cursor until the session is closed. For the transient AdminUI picker session this is low-risk (the session is short-lived and DisposeAsync → CloseAsync eventually cleans it up server-side), but it represents a server-side resource leak during cancellation.
The identical pattern exists in the runtime driver (OpcUaClientDriver.cs), making this a cross-cutting concern rather than a module-local defect.
Recommendation: Catch OperationCanceledException in the pagination loop, issue a fire-and-forget BrowseNext(releaseContinuationPoints: true, continuationPoints: [cp]) before re-throwing, and apply the same fix to Driver.OpcUaClient.
Resolution: Deferred — awaiting a cross-cutting fix that also updates Driver.OpcUaClient. Short session lifetime of the AdminUI picker limits practical impact.
Re-review 2026-06-19
Commit 7e1f34da. Single targeted finding surfaced during code review.
Driver.OpcUaClient.Browser-003
| Field | Value |
|---|---|
| Severity | Medium |
| Category | Correctness & logic bugs / OtOpcUa conventions |
| Location | src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientDriverBrowser.cs:19 |
| Status | Resolved |
Description: OpcUaClientDriverBrowser's static JsonOpts (JsonSerializerOptions) was constructed without a JsonStringEnumConverter. AdminUI emits enum-valued driver-config fields (SecurityPolicy, SecurityMode, AuthType, TargetNamespaceKind) as their string names (e.g. "AuthType":"Certificate", "SecurityPolicy":"Basic256Sha256"). Without the converter, System.Text.Json throws a JsonException during deserialization of any such config, surfacing a confusing parse error instead of the browser's own domain-specific validation message.
This is the same systemic enum-serialization bug fixed in the factory (OpcUaClientDriverFactoryExtensions.JsonOptions) and probe (OpcUaClientDriverProbe._opts), both of which carry the converter with an explicit comment stating the browser must parse configs the same way. The browser was the one site left unpatched.
Recommendation: Add new JsonStringEnumConverter() to the browser's JsonSerializerOptions.Converters, matching the factory/probe pattern exactly. JsonStringEnumConverter accepts both string names and numeric ordinals, so existing numeric-authored configs require no migration.
Resolution: Fixed 2026-06-19 (SHA 7e1f34da working tree) — added new JsonStringEnumConverter() to OpcUaClientDriverBrowser.JsonOpts.Converters and imported System.Text.Json.Serialization; regression tests OpenAsync_with_string_enum_AuthType_deserializes_correctly and OpenAsync_with_numeric_enum_AuthType_still_works added to the unit test project.