Files
histsdk/docs/reverse-engineering/wcf-non-analog-tag-create.md
T
Joseph Doherty c1b1b3d23b R1.11 DelTep capture + R1.3/R1.4/R1.12/R1.13 bounded out
DelTep (extended-property delete) — wire format captured + serializer
golden-proven, but live delete is server-blocked and NOT exposed publicly:
- Captured the DelTep inBuff via a cross-session trick (harness add-tep gains
  --tep-skip-add + read-for-sync before --tep-delete; Capture-DeleteTagExtended
  Properties.ps1 / decode-del-tep-capture.py). Layout = same group framing as
  AddTEx but property-name-only (no 0x43 value) + 0x00 group trailer.
- SerializeDeleteRequest + 4 golden tests pin the server-accepted buffer.
- A decisive experiment shows SDK-added properties ARE deletable (the native
  client read-syncs and deletes one), so SDK-add is complete; the SDK's own
  DelTep is rejected by CHistStorage::DeleteTagExtendedProperties even with
  byte-identical inBuff, matching mode/handle, GetTgByNm+GetTepByNm prime, open
  channel, and 60s retries. Root cause: the native multiplexes services over one
  connection (per-connection working set); the SDK's per-service WCF channels
  don't reproduce it. Kept as documented-but-blocked internal orchestrator path;
  no public HistorianClient delete API.

Bounded out with evidence (no code; docs + roadmap + probe):
- R1.12 localized-property write — no op on 2020 (mirror of R1.6); no
  *LocalizedPropert*/TagLocalized* symbol in any current/*.dll.
- R1.13 non-analog tag create — GATED; native AddTag rejects every non-analog
  type client-side (ValidationFailed, before any WCF op): SingleByteString,
  DoubleByteString, Int1 all fail, Float works. No Discrete type in the native
  enum, no TagType setter. No wire request to capture.
- R1.3 timezone + R1.4 EventStorageMode — re-confirmed 2023R2/gRPC-only from
  the Runtime DB schema (no timezone param, no EventStorageMode anywhere) and a
  parameter-op probe (GetSystemParameter + GETRP return null/throw for every
  candidate; only HistorianVersion works).

238 unit tests pass; full solution builds with 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
2026-06-21 11:26:21 -04:00

4.3 KiB

Non-analog tag create over 2020 WCF — GATED (HCAL R1.13)

Status: bounded out (2026-06-21). No non-analog (string / discrete / wide-integer) tag-create path is reachable on 2020 — the native managed client rejects every non-analog type client-side, before any WCF op, so there is no wire format to capture and nothing to implement against. EnsureTagAsync stays analog-only (Float, Double, Int2, UInt2, Int4, UInt4); unsupported types throw ProtocolEvidenceMissingException from HistorianTagWriteProtocol.GetAnalogDataTypeCode.

What R1.13 asked for

Create string / discrete (non-analog) tags via History.EnsureTags, with a distinct CTagMetadata variant. The roadmap flagged it "⚠ native AddTag rejected some types — confirm server path first; may be GATED."

Findings (live-probed against the local 2020 Historian)

  1. No discrete/boolean data type exists. The native ArchestrA.HistorianDataType enum (current/aahClientManaged.dll, dumped via enum-dump) has exactly 12 members: Int1, Int2, UInt2, Int4, UInt4, Float, Double, SingleByteString, DoubleByteString, Event, Structure. There is no Discrete/Boolean, and no Int8/UInt8/UInt1/Guid/FileTime (those are SDK-only extensions in Models/HistorianDataType, recovered from the C++ CDataType predicate IL — they are not settable on the managed HistorianTag).

  2. Tag type is data-type-derived, not separately settable. ArchestrA.HistorianTag (--dump-type-members) has no TagType property — only TagDataType. It does carry the discrete/string-shaped fields (MessageOn/MessageOff, RolloverValue, dead-band/interpolation), and the type exposes ValidateAnalog*, ValidateDiscreteGeneralProperties, and ValidateDiscreteAndStringStorageProperties — but the analog-vs-discrete-vs-string decision is made internally from the data type, with no way to request "discrete."

  3. Native AddTag rejects every non-analog type client-side. Driving the native HistorianAccess.AddTag(HistorianTag, …) (harness write scenario, --write-data-type) against the live server:

    Data type AddTag.Success ErrorCode ErrorType
    SingleByteString false ValidationFailed CustomError
    DoubleByteString false ValidationFailed CustomError
    Int1 false ValidationFailed CustomError
    Int8 / UInt8 n/a not in the native enum
    Float (control) true Success

    The error — ErrorType=CustomError, ErrorCode=ValidationFailed, ErrorDescription="Transaction validation failed" — is raised by the client's own Validate* chain before any WCF message is sent (the wrapper even auto-populates discrete defaults MessageOn=ON/MessageOff=OFF, then fails validation). So the native client never emits a non-analog EnsT2/AddTag request.

Why it's not deliverable here

Because the native client refuses non-analog types client-side, no wire request exists to reverse-engineer — there is no captured CTagMetadata variant for string or discrete tags, and the SDK does not guess wire bytes. The rejection is not string-specific: Int1 (a non-string integer outside the analog set {Float, Double, Int2, UInt2, Int4, UInt4}) fails identically, so the boundary is "the analog set" rather than "strings only." Creating string/discrete tags on 2020 evidently goes through a different subsystem (e.g. the configuration editor / SQL config path), not this client's AddTag. R1.13 is closed as GATED, consistent with the mission note that these types "fail at native AddTag — likely require a different path and are intentionally not supported."

Probe commands (read-only / sandbox-guarded)

dotnet run --project tools\AVEVA.Historian.ReverseEngineering -- enum-dump current\aahClientManaged.dll HistorianDataType
dotnet run --project tools\AVEVA.Historian.ReverseEngineering -- dnlib-method current\aahClientManaged.dll HistorianTag.ValidateDataType
# native AddTag probe (sandbox tag must start with RetestSdkWrite; --write-skip-add-value avoids the blocked value path):
dotnet run --project tools\AVEVA.Historian.NativeTraceHarness -- --scenario write --server-name localhost --tcp-port 32568 \
  --write-sandbox-tag RetestSdkWriteNADoubleByteString --write-data-type DoubleByteString --write-skip-add-value