docs(plans): sync native AB transport plan + tasks.json after task transcription fixes
This commit is contained in:
@@ -1628,7 +1628,7 @@ Implement `MultipleServicePacket`, `SymbolObjectServices` and `TemplateObjectSer
|
||||
- [ ] `PcccServices.Plc5TypedRead(tns, addr, elements)` → `0F 00 <tns u16> 68 <packetOffset u16 0> <totalTrans u16 elements> <PLC-5 system address> <size u16 elements>`.
|
||||
- The system address is a level-mask byte (bit0 = file level present? **derive from `plc5-read-n7.hex`**) followed by level values (`$N7:0` ⇒ data table section, file 7, element 0), each `u8*` encoded.
|
||||
- `Plc5TypedWrite` is `0F 00 <tns> 67 <offset u16> <total u16> <sysaddr> <type/size param> <data>`.
|
||||
- ⚠️ The PLC-5 framing must be **copied from the capture**. If ab_server's `--plc=PLC/5` path is not exercised by libplctag in the capture, mark the PLC-5 builders `[Obsolete("Unverified — no capture")]`, make the native AbLegacy runtime throw `AbTransportException(Unsupported)` for `plc5`, and record that in the Task 20 docs. Do not ship unverified framing.
|
||||
- ⚠️ The PLC-5 framing must be **copied from the capture**. If ab_server's `--plc=PLC/5` path is not exercised by libplctag in the capture, do **not** ship PLC-5 builders at all. Instead expose `PcccServices.Plc5Verified = false`, have the native AbLegacy runtime throw `AbTransportException(Unsupported)` for `plc5`, and record that in the Task 20 docs. Do not use `[Obsolete]`: under `TreatWarningsAsErrors` it is a build error at every call site.
|
||||
- [ ] `PcccServices.ParseReply(cipReply)` strips the requestor-id echo (`len u8` + `len-1` bytes), reads `cmd u8 (0x4F), sts u8, tns u16`, then `extSts u8` if `sts == 0xF0`, then the data. Non-zero STS → `AbTransportException(AbStatusCodes.FromPccc(sts, ext))`. A TNS that differs from the request → `BadReply`.
|
||||
- [ ] Capture parity: `SlcTypedRead` for `N7:0` / `F8:0` equals the PCCC payload inside the captured Execute PCCC request, with the TNS normalized; `ParseReply` on the captured reply yields the 2 / 4 data bytes.
|
||||
|
||||
@@ -2219,7 +2219,7 @@ public sealed class LibplctagConfinementGuardTests
|
||||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriverProbe.cs` (pass `UseConnectedMessaging: profile.SupportsConnectedMessaging`)
|
||||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs` (`EnsureTagRuntimeAsync` passes `AuthoredType: def.DataType`; `DeviceState`'s create-params builder passes `UseConnectedMessaging: Profile.SupportsConnectedMessaging`)
|
||||
- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/NativeAbCipTagRuntimeTests.cs`
|
||||
- Modify: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests.csproj` (add a `ProjectReference` to Ab.Transport and `<Compile Include="..\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests\Fakes\FakeCipChannel.cs" Link="Fakes\FakeCipChannel.cs" />`. Source-linking compiles the fake into this assembly, so it stays `internal`. Also add `InternalsVisibleTo` for `ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests` to the Ab.Transport csproj if the tests touch internal transport types)
|
||||
- Modify: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests.csproj` (add a `ProjectReference` to Ab.Transport and `<Compile Include="..\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests\Fakes\FakeCipChannel.cs" Link="Fakes\FakeCipChannel.cs" />`.; source-linking compiles the fake into this assembly, so it stays `internal`)
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] `NativeAbCipDevice.Key(p)` = `new DeviceKey(p.Gateway, p.Port > 0 ? p.Port : 44818, p.CipPath, p.LibplctagPlcAttribute)`.
|
||||
@@ -2250,7 +2250,7 @@ public sealed class LibplctagConfinementGuardTests
|
||||
- [ ] `Dispose` releases the lease, waiting for at most 2 s (`lease.DisposeAsync().AsTask().Wait(TimeSpan.FromSeconds(2))`). It must not fire and forget: an immediate re-init would otherwise open a second session to the press while the old Forward Close is still in flight (review finding 22). It never disposes the shared scheduler; the pool does that on the last release.
|
||||
- [ ] `NativeAbCipTagFactory : IAbCipTagFactory` creates `NativeAbCipTagRuntime`.
|
||||
- [ ] Test seam: `internal static NativeAbCipTagRuntime ForTest(ICipChannel channel, AbCipTagCreateParams p)` builds a runtime over the given channel with a private `RequestScheduler(channel, p.AllowPacking)` (test-only; no pool lease). Production code always goes through the factory and `DeviceSessionPool.Shared`.
|
||||
- [ ] **Unit tests** (against `FakeCipChannel` injected through an `internal` constructor that takes an `ICipChannel`):
|
||||
- [ ] **Unit tests** (against `FakeCipChannel` via `ForTest`):
|
||||
- decode parity for each type, using a buffer captured from `LibplctagTagRuntime` semantics (hard-coded bytes);
|
||||
- the TypeMismatch case (REAL returned for a DInt-authored tag);
|
||||
- an unknown tag ⇒ `InitializeAsync` throws `AbTransportException(NotFound)`;
|
||||
@@ -2307,11 +2307,11 @@ public async Task Unknown_tag_fails_initialize_with_NotFound()
|
||||
- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/NativeAbCipTemplateReaderTests.cs`
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] `NativeAbCipTagEnumerator` reuses the `LibplctagTagEnumerator(Func<AbCipTagCreateParams, string, CancellationToken, Task<byte[]>> readListing)` seam. Its listing reader:
|
||||
- [ ] **Enumerator split.** The current `LibplctagTagEnumerator` class (its listing loop has no libplctag dependency apart from its default constructor) is renamed **`SymbolListingEnumerator`**, keeping its `Func<AbCipTagCreateParams, string, CancellationToken, Task<byte[]>> 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)`.
|
||||
- [ ] **Native listing reader.** `NativeAbCipTagEnumerator` = `new SymbolListingEnumerator(NativeListingReader.ReadAsync)`, wrapped in a class for the factory. Its reader:
|
||||
- `"@tags"` ⇒ pages `SymbolObjectServices.GetInstanceAttributeList(null, start)` from instance 0, appending entry bytes. While the reply is `More`, it continues from `LastInstance + 1`.
|
||||
- `"Program:X.@tags"` ⇒ the same with `programName: "X"`.
|
||||
- It returns the concatenated bytes.
|
||||
- So `NativeAbCipTagEnumerator` is `new LibplctagTagEnumerator(NativeListingReader.ReadAsync)`, wrapped in a class for the factory. **Rename `LibplctagTagEnumerator` → `SymbolListingEnumerator`** (it has no libplctag dependency apart from its default constructor). Move the libplctag default reader into `LibplctagTagEnumerator.cs` as `LibplctagListingReader`, so the guard (Task 11) still allows it.
|
||||
- [ ] `NativeAbCipTemplateReader.ReadAsync(params, id)`:
|
||||
- `GetAttributes(id)` gives `defWords`, `structBytes`, `members`, `handle`.
|
||||
- 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.
|
||||
@@ -2356,7 +2356,7 @@ public async Task Unknown_tag_fails_initialize_with_NotFound()
|
||||
- Byte counts per element: N/A/B/I/O/S = 2, F/L = 4, T/C/R = 6 when no sub-element (the whole element), 2 when a word sub-element is chosen, ST = 84.
|
||||
- Element count = `p.ElementCount`.
|
||||
- [ ] **Read and write:**
|
||||
- `ReadAsync` dispatches by `plc` attribute: `slc500`/`micrologix`/`logixpccc` ⇒ `SlcTypedRead`; `plc5` ⇒ `Plc5TypedRead`, or `AbTransportException(Unsupported)` if Task 8 marked PLC-5 unverified.
|
||||
- `ReadAsync` dispatches by `plc` attribute: `slc500`/`micrologix`/`logixpccc` ⇒ `SlcTypedRead`; `plc5` ⇒ `Plc5TypedRead`, or `AbTransportException(Unsupported)` when `PcccServices.Plc5Verified` is false (Task 8).
|
||||
- The TNS increments per request.
|
||||
- **Chunking (review finding 12):** a read or write whose byte count exceeds `PcccServices.MaxDataBytes` (236) is split into consecutive element ranges of at most 236 bytes (118 words, or 59 longs/floats). The element number advances per chunk and the data is concatenated in order. A failure in any chunk fails the whole operation with that chunk's status; a partial write is reported as failed, never as Good. Test a 256-word array read (three chunks: 118 + 118 + 20) and a 130-word write (two chunks).
|
||||
- On failure it stores the status and rethrows.
|
||||
@@ -2508,7 +2508,7 @@ _templateReaderFactory = templateReaderFactory ?? defaults.Templates;
|
||||
- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/TransportMatrix.cs`
|
||||
- Modify: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyReadSmokeTests.cs` and siblings
|
||||
- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/TransportMatrix.cs`
|
||||
- Create: `docs/plans/2026-10-07-native-ab-transport-gate-results.md` (results table; Tasks 18 and 21 append to it)
|
||||
- Modify: `docs/plans/2026-10-07-native-ab-transport-gate-results.md` (created empty by Task 1; this task fills only its `## ab_server suites (T17)` section)
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] `TransportMatrix.Profiles` yields `(profile, transport)` for every profile × both transports. The theories switch from `MemberData(nameof(Profiles))` to `MemberData(nameof(TransportMatrix.Profiles), MemberType = typeof(TransportMatrix))`, and options get `Transport = transport`.
|
||||
@@ -2517,10 +2517,10 @@ _templateReaderFactory = templateReaderFactory ?? defaults.Templates;
|
||||
- AbCip: `controllogix`, `compactlogix`, `micro800`, `guardlogix`.
|
||||
- AbLegacy: `slc500`, `micrologix`, `plc5`.
|
||||
- **For each profile, the set of passing tests under `Native` ⊇ the set under `Libplctag`.**
|
||||
- [ ] Any native-only failure is fixed in the transport task's code (reopen that task's files) and is not skipped. The only allowed exception is the PLC-5 `Unsupported` path from Task 8, marked `Skip = "PLC-5 framing unverified — see Task 8"` **only if** Task 8 marked it.
|
||||
- [ ] Any native-only failure is fixed in the transport task's code (reopen that task's files) and is not skipped. The only allowed exception is the PLC-5 `Unsupported` path from Task 8, marked `Skip = "PLC-5 framing unverified — see Task 8"` **only if** Task 8 set `Plc5Verified = false`.
|
||||
- [ ] The results doc records, under its `## ab_server suites (T17)` heading, per profile × transport, the pass/fail/skip counts plus the `ab_server` image tag and the commit.
|
||||
|
||||
**Verify:** for each profile P: `ssh dohertj2@10.100.0.35 'cd /opt/otopcua-abcip && docker compose --profile P up -d'` then `AB_SERVER_ENDPOINT=10.100.0.35:44818 dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests --logger "trx;LogFileName=P.trx"` → the Native pass set ⊇ the Libplctag pass set (compare the two trx files with `grep -o 'testName="[^"]*" .*outcome="[^"]*"'`).
|
||||
**Verify:** for each profile P (AbCip stack directory as established in Task 4's `Captures/README.md`; AbLegacy at `~/otopcua-ablegacy` with `AB_LEGACY_COMPOSE_PROFILE=P`): `ssh dohertj2@10.100.0.35 'cd <abcip stack dir> && docker compose --profile P up -d'` then `AB_SERVER_ENDPOINT=10.100.0.35:44818 dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests --logger "trx;LogFileName=P.trx"` → the Native pass set ⊇ the Libplctag pass set (compare the two trx files with `grep -o 'testName="[^"]*" .*outcome="[^"]*"'`).
|
||||
|
||||
**Steps:**
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user