diff --git a/docs/plans/2026-10-07-native-ab-transport.md.tasks.json b/docs/plans/2026-10-07-native-ab-transport.md.tasks.json index 391ebdfd..4cde65e5 100644 --- a/docs/plans/2026-10-07-native-ab-transport.md.tasks.json +++ b/docs/plans/2026-10-07-native-ab-transport.md.tasks.json @@ -116,7 +116,7 @@ { "id": 13, "subject": "Task 13: Native AbCip symbol enumerator + template reader", - "status": "pending", + "status": "completed", "description": "**Goal:** Native `IAbCipTagEnumerator` and `IAbCipTemplateReader` implementations whose output buffers feed the **unchanged** decoders.\n\n**Files:**\n- Create: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/SymbolListingEnumerator.cs` (the transport-neutral listing loop moved out of `LibplctagTagEnumerator.cs`)\n- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagEnumerator.cs` (keeps only `LibplctagListingReader` + `LibplctagTagEnumeratorFactory`)\n- Modify: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/` files referencing `LibplctagTagEnumerator` (rename to `SymbolListingEnumerator`)\n- Create: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/NativeAbCipTagEnumerator.cs`\n- Create: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/NativeAbCipTemplateReader.cs`\n- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/NativeAbCipTagEnumeratorTests.cs`\n- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/NativeAbCipTemplateReaderTests.cs`\n\n**Acceptance Criteria:**\n- [ ] **Enumerator split.** The current `LibplctagTagEnumerator` class (its listing loop has no libplctag dependency apart from its default constructor) is renamed **`SymbolListingEnumerator`**, keeping its `Func> readListing` constructor seam. The libplctag default reader moves into `LibplctagTagEnumerator.cs` as `LibplctagListingReader`, so the Task 11 guard still allows it. `LibplctagTagEnumeratorFactory` returns `new SymbolListingEnumerator(LibplctagListingReader.ReadAsync)`.\n- [ ] **Native listing reader.** `NativeAbCipTagEnumerator` = `new SymbolListingEnumerator(NativeListingReader.ReadAsync)`, wrapped in a class for the factory. Its reader:\n - `\"@tags\"` \u21d2 pages `SymbolObjectServices.GetInstanceAttributeList(null, start)` from instance 0, appending entry bytes. While the reply is `More`, it continues from `LastInstance + 1`.\n - `\"Program:X.@tags\"` \u21d2 the same with `programName: \"X\"`.\n - It returns the concatenated bytes.\n- [ ] `NativeAbCipTemplateReader.ReadAsync(params, id)`:\n - `GetAttributes(id)` gives `defWords`, `structBytes`, `members`, `handle`.\n - It then sends `ReadTemplate(id, offset, chunk)` repeatedly, with `chunk = Math.Min(remaining, MaxRequestBytes - 16)`, where the total = `defWords * 4 - 23` (the CIP-documented Read Template length), advancing by each reply's data length until the status is 0. Status `0x06` continues.\n - It returns `TemplateObjectServices.BuildDecoderBuffer(...)`.\n- [ ] Unit tests feed the **real controller captures** through the native path. The fake channel serves, as reply data, the bytes contained in `CipTemplateObjectWireCaptureTests` (rev 34.11 press). The test asserts that `CipTemplateObjectDecoder.Decode(nativeBuffer)` equals `Decode(capturedLibplctagBuffer)`, member by member.\n - The captured libplctag buffer's header **is** the 14-byte header, so the fake replies are built from that header's fields plus the raw bytes after byte 14.\n - Do the same for `CipSymbolObjectRealControllerCaptureTests` entries: split them across two pages with a `0x06` status between, and assert the decoded tag list is identical.\n- [ ] Both factories (`NativeAbCipTagEnumeratorFactory`, `NativeAbCipTemplateReaderFactory`) exist and lease from `DeviceSessionPool.Shared` via `NativeAbCipDevice`. They send through **the lease's shared `Scheduler`**, never a private one, and dispose the lease (bounded 2 s wait, as in T12) when the enumeration or read completes.\n\n**Verify:** `dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests --filter \"FullyQualifiedName~NativeAbCipTagEnumerator|FullyQualifiedName~NativeAbCipTemplateReader|FullyQualifiedName~Discovery\"` \u2192 pass.\n\nSteps + code: docs/plans/2026-10-07-native-ab-transport.md \u00a7 Task 13.\n\n```json:metadata\n{\"files\": [\"src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagEnumerator.cs\", \"src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/NativeAbCipTagEnumerator.cs\", \"src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/NativeAbCipTemplateReader.cs\", \"src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/SymbolListingEnumerator.cs\", \"tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/\", \"tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/NativeAbCipTagEnumeratorTests.cs\", \"tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/NativeAbCipTemplateReaderTests.cs\"], \"verifyCommand\": \"dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests --filter \\\"FullyQualifiedName~NativeAbCipTagEnumerator|FullyQualifiedName~NativeAbCipTemplateReader|FullyQualifiedName~Discovery\\\"\", \"modelTier\": \"standard\", \"model\": \"sonnet\", \"acceptanceCriteriaCount\": 4}\n```", "blockedBy": [ 2,