write-commands plan: Phase 2 partial - capture EnsT2(Float) wire bytes
Per plan §1 in scope: EnsT2 for analog tags, AddS2, DelT.
Per plan §2 safety: localhost only, single sandbox tag
RetestSdkWriteSandbox, harness refuses any name not starting with
RetestSdkWrite, time-bounded writes, ReadOnly=false only when scenario
is "write".
Phase 2 actually executed:
1. tools/AVEVA.Historian.NativeTraceHarness/Program.cs extended with
--scenario write. New args:
--write-sandbox-tag <name> (default RetestSdkWriteSandbox)
--write-value <numeric> (default 42.5)
--write-data-type <name> (default Float)
--write-delete-after (best-effort cleanup)
Toggles ConnectionArgs.ReadOnly=false when scenario is "write" so
the connection accepts the write attempt instead of rejecting at
the harness boundary with error 132 "Operation is not enabled".
2. Sandbox tag RetestSdkWriteSandbox created in Runtime DB
(wwTagKey=240, AcquisitionType=2 Manual, StorageType=1 Cyclic)
via the harness's AddTag call. Single dedicated tag per safety §1.
3. Captured the full write-flow wire sequence at
artifacts/reverse-engineering/instrumented-wcf-writemessage-writes/
bothmessage-write-capture-latest.ndjson (46 records, 23 outgoing +
23 incoming).
The chain is identical to the event flow except:
- EnsT2 payload is the 146-byte analog CTagMetadata instead of
the 83-byte event one
- NO RTag2 between Open2 and EnsT2 (events used RTag2 with
CmEventTagId)
4. The 146-byte analog CTagMetadata layout is dumped in the plan doc
for layout decoding. Visible fields (still being aligned against
CTagUtil.ConvertTagMetadataToHistorianTag IL at token 0x060055CE):
- tag name "RetestSdkWriteSandbox" (compact ASCII, len 21)
- 16 bytes of FF (CommonArchestraEventTypeId placeholder unused
for analog?)
- description "SDK write-RE sandbox tag" (compact ASCII, len 24)
- metadata provider "MDAS" (compact ASCII)
- engineering unit "test" (compact ASCII)
- Int64 FILETIME (date-created, year 2026)
- uint32 0x2710 = 10000 (storage-related, possibly StorageRate)
- double 1.0 (likely IntegralDivisor or scaling factor)
- 5-byte trailer FE 00 01 01 01 (matches event tag's
2F 27 01 01 01 shape)
5. AddS2 BLOCKED CLIENT-SIDE at error 168 "Tag not added to server".
Native AddStreamedValue refuses to send because the tag isn't in
the server's session cache, even though EnsT2 created it in the
Runtime DB. Likely needs RTag2(analog tag GUID) prereq similar
to the event flow's RTag2(CmEventTagId), or one of
aahClientCommon.CHistStorage.AddTagidPairs (token 0x0600202F) or
AddTagsWithServerTagId (token 0x06002026). AddS2 wire bytes NOT
captured this session.
6. scripts/decode-write-capture.py — sanitized decoder for the
capture, walks the 46 records and dumps the EnsT2 InBuff bytes
for layout work. No identity strings; only sandbox-chosen values
appear in output.
Phase 2 remaining work documented in the plan doc as a 5-item
checklist for the next session:
1. Decode the AddS2 prereq (likely RTag2 with analog tag GUID).
2. Capture AddS2 wire bytes once prereq is satisfied.
3. Implement HistorianAddTagsProtocol.SerializeAnalog/Discrete/
String CTagMetadata variants.
4. Implement HistorianAddStreamValuesProtocol.Serialize.
5. Implement public surface: EnsureTagAsync, WriteValueAsync,
DeleteTagAsync (golden-byte + gated live integration tests).
No SDK source changed — implementation deferred until AddS2 wire
bytes are in hand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,104 @@
|
||||
# Plan: Reverse-Engineering Write Commands
|
||||
|
||||
Status: **PHASE 1 EXECUTED on 2026-05-04 — discovery complete, awaiting
|
||||
operator decision on Phase 2.** No code changes; no DB writes.
|
||||
Status: **PHASE 2 PARTIALLY EXECUTED on 2026-05-04** — write-scenario
|
||||
harness extension built and captured the full EnsT2(Float) wire byte
|
||||
sequence against a real sandbox tag. AddS2 is blocked client-side by
|
||||
"Tag not added to server" (error 168) — the native `AddStreamedValue`
|
||||
refuses to send because the tag isn't in the server's session cache,
|
||||
even though `EnsT2` created it in the Runtime DB. AddS2 wire bytes
|
||||
**not yet captured**; needs a separate session to resolve the
|
||||
post-EnsT2 registration prereq (likely RTag2 with the analog tag GUID,
|
||||
mirroring the event flow's RTag2(CmEventTagId)).
|
||||
|
||||
## Phase 2 results
|
||||
|
||||
**Sandbox tag created** in Runtime DB: `RetestSdkWriteSandbox`,
|
||||
wwTagKey=240, DateCreated=2026-05-04 07:49:50. Single dedicated tag
|
||||
per safety §1; no other tags touched.
|
||||
|
||||
**`tools/AVEVA.Historian.NativeTraceHarness/Program.cs` extended** with
|
||||
`--scenario write`:
|
||||
|
||||
- New args: `--write-sandbox-tag <name>` (default
|
||||
`RetestSdkWriteSandbox`; refuses any name that doesn't start with
|
||||
`RetestSdkWrite`), `--write-value <numeric>` (default 42.5),
|
||||
`--write-data-type <name>` (default Float), `--write-delete-after`
|
||||
(best-effort cleanup).
|
||||
- Toggles `ConnectionArgs.ReadOnly` to false when scenario is `write`
|
||||
(otherwise the connection rejects writes with error 132 "Operation
|
||||
is not enabled").
|
||||
- Calls `ArchestrA.HistorianAccess.AddTag` (drives `EnsT2` on the wire),
|
||||
then `ArchestrA.HistorianAccess.AddStreamedValue` (would drive
|
||||
`AddS2` but currently aborts client-side at error 168).
|
||||
- Resolves the actual `wwTagKey` via SQL when `AddTag` returns 0
|
||||
because the tag already exists from a prior session.
|
||||
- Public `AddStreamedValue` overload selector: instance method whose
|
||||
signature is `(HistorianDataValue, …, HistorianAccessError&)` —
|
||||
picks the simplest dispatcher that's actually reflectable (the
|
||||
4-param impl is private and not visible to reflection).
|
||||
|
||||
**Captures landed** at
|
||||
`artifacts/reverse-engineering/instrumented-wcf-writemessage-writes/bothmessage-write-capture-latest.ndjson`
|
||||
(46 records: 23 outgoing + 23 incoming). Same priming chain as the
|
||||
event flow:
|
||||
|
||||
```
|
||||
Hist.GetV → Hist.GetI ×2 → Hist.ValCl ×2 → Hist.Open2 →
|
||||
Stat.GetV ×2 → Stat.GETHI ×2 → Hist.UpdC3 →
|
||||
Stat.GetSystemParameter ×7 → Trx.GetV → Stat.GetV → Retr.GetV →
|
||||
Hist.EnsT2(Float) → Hist.Close2
|
||||
```
|
||||
|
||||
No `RTag2`. The chain identical to the event flow except the EnsT2
|
||||
payload is the analog CTagMetadata instead of the event one, and there
|
||||
is NO RTag2 between Open2 and EnsT2 (events used RTag2 to register
|
||||
`CmEventTagId`).
|
||||
|
||||
**Native EnsT2(Float) request body** (record 42, 322 bytes total; the
|
||||
146-byte CTagMetadata `InBuff` payload is the new evidence target):
|
||||
|
||||
```text
|
||||
67 03 00 01 00 00 00 04 C6 02 01 00 00 00 00 00
|
||||
00 00 00 00 00 00 00 00 00 00 00 09 15 00 52 65
|
||||
74 65 73 74 53 64 6B 57 72 69 74 65 53 61 6E 64
|
||||
62 6F 78 FF FF FF FF FF FF FF FF FF FF FF FF FF
|
||||
FF FF FF 09 18 00 53 44 4B 20 77 72 69 74 65 2D
|
||||
52 45 20 73 61 6E 64 62 6F 78 20 74 61 67 09 04
|
||||
00 4D 44 41 53 02 01 01 00 00 00 01 E8 03 00 00
|
||||
D6 00 0E 4F BC DB DC 01 1A 03 09 04 00 74 65 73
|
||||
74 10 27 00 00 00 00 00 00 00 00 F0 3F FE 00 01
|
||||
01 01
|
||||
```
|
||||
|
||||
Visible fields (still being decoded against the
|
||||
`CTagUtil.ConvertTagMetadataToHistorianTag` IL at token `0x060055CE`):
|
||||
|
||||
- `09 15 00 RetestSdkWriteSandbox` (compact ASCII tag name, len 21)
|
||||
- 16 bytes of `FF` — possibly a placeholder/sentinel for `CommonArchestraEventTypeId`-equivalent that's not used for analog
|
||||
- `09 18 00 SDK write-RE sandbox tag` (compact ASCII description, len 24)
|
||||
- `09 04 00 MDAS` (compact ASCII metadata provider)
|
||||
- `09 04 00 test` (compact ASCII engineering unit)
|
||||
- `0E 4F BC DB DC 01 1A 03` byte-pattern looks like an Int64 FILETIME (date-created ~2026)
|
||||
- `10 27 00 00` = uint32 0x2710 = 10000 (storage-related)
|
||||
- `00 00 00 00 00 00 F0 3F` = double 1.0 (likely IntegralDivisor or similar scaling)
|
||||
- `FE 00 01 01 01` = trailer (matches event tag's `2F 27 01 01 01` shape)
|
||||
|
||||
**Decoder script** at `scripts/decode-write-capture.py` for the next
|
||||
session.
|
||||
|
||||
## Phase 2 remaining work
|
||||
|
||||
1. Decode the AddS2 prereq — find what RegisterTag / AddTagPair call
|
||||
the server expects between EnsT2 and AddS2. Likely
|
||||
`aahClientCommon.CHistStorage.AddTagidPairs` (token `0x0600202F`)
|
||||
or `AddTagsWithServerTagId` (token `0x06002026`).
|
||||
2. Capture AddS2 wire bytes once the prereq is satisfied.
|
||||
3. Implement `HistorianAddTagsProtocol.SerializeAnalogCTagMetadata` /
|
||||
discrete / string variants from the 146-byte capture above.
|
||||
4. Implement `HistorianAddStreamValuesProtocol.Serialize` from the
|
||||
yet-to-capture AddS2 bytes.
|
||||
5. Implement the public surface: `EnsureTagAsync`, `WriteValueAsync`,
|
||||
`DeleteTagAsync` (golden-byte + gated live integration tests).
|
||||
|
||||
## Phase 1 findings (recorded here, not implementing)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user