Files
histsdk/docs/reverse-engineering/wcf-status-localhost.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

6.0 KiB

WCF Status Evidence

Commands:

dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-status localhost 32568
dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-status localhost 32568 Version

Confirmed:

  • The local status endpoint is net.tcp://localhost:32568/Stat.
  • IStatusServiceContract2 is a static WCF contract named Stat in namespace aa. The managed definitions now include GetSystemParameter, GETHI, PNGS, and PNGP.
  • GetInterfaceVersion returns code 0, version 0 on the local 2020 install.
  • The decompiled CStatusConnectionWCF.GetServerTime implementation is a WCF-path stub that returns success without calling the Stat service. The managed direct call likewise returns code 0 with size 0 and no buffer.

Observed sanitized localhost results:

  • GetSystemTimeZoneName(handle: 0) returns code 4 and no value.
  • IsDBCaseSensitive(handle: 0) returns code 4.
  • GetSystemParameter(handle: 0, "Version") returns false with no error buffer.

Re-tested 2026-06-20 with a real authenticated client handle (full Open2 auth chain), not handle: 0:

  • GetSystemParameter(handle, "HistorianVersion") → real version string (works; shipped as GetSystemParameterAsync).
  • GetSystemTimeZoneName(handle) → return code 0x00000000 (success) but an empty value string. Same channel/handle that makes GetSystemParameter return real data, so this is the op's own behavior, not an auth/marshalling gap. GetSystemTimeZoneName is a member of the GetServerTime stub family: the 2020 WCF path returns success without producing a value (the native client computes the zone locally). It only becomes a real round-trip on the 2023 R2 gRPC front door (Status.GetSystemTimeZoneName), which is absent on this box.

Interpretation:

  • Stat endpoint routing is confirmed, but status operations that require a real client handle are not usable until managed session open is solved.
  • GetServerTime should not be promoted into the public SDK as a real server time call from this WCF path; native evidence shows it is a no-op stub here.
  • GetServerTimeZoneAsync (roadmap R1.3) is NOT a trivial WCF op on 2020 — it is a stub returning empty. Do not ship it over the 2020 WCF transport. Deliver it only against a live 2023 R2 gRPC server. Reclassified in docs/plans/hcal-roadmap.md.

GETRP / GetRuntimeParameter (roadmap R1.2) — DONE, live-verified 2026-06-20

Captured the native HistorianAccess.GetRuntimeParameter(List<string>, out List<object>) WCF traffic with scripts/Capture-RuntimeParam.ps1 (instrument-wcf-{write,read}message). Findings:

  • The WCF op is aa/Stat/GETRPbool GETRP(string handle, byte[] pRequestBuff, out byte[] pResponseBuff, out byte[] errorBuffer), i.e. the same string-handle + request/response-buffer shape as GETHI, not the simple GetSystemParameter(uint, string) shape the roadmap originally assumed.
  • The string handle is the Open2 storage-session GUID (the value ParseOpenConnectionResponse reads from outBuff[5..21]), sent UPPERCASE, dash-separated, no braces (ToString("D").ToUpperInvariant()).
  • Unlike GETHI (which the earlier probe found blocked), GETRP succeeds from the pure-managed client with that handle: GetRuntimeParameter("HistorianVersion")20,0,000,000.
  • pRequestBuff = 54 67 01 00 (sig+version) + uint nameCount + per name(uint charCount + UTF-16LE). pResponseBuff = version(1) + uint resultCount + CRetVariant(0x43 VT_BSTR + uint16 payloadLen + uint16 charCount + UTF-16LE).

Shipped as HistorianClient.GetRuntimeParameterAsync(name). See HistorianRuntimeParameterProtocol, golden WcfRuntimeParameterProtocolTests, and the handle-format lead in wcf-string-handle-wall.md §Update (retry GETHI/ExeC uppercased).

R1.3 timezone + R1.4 EventStorageMode — re-confirmed bounded out (2026-06-21)

Both were already classified 2023R2/gRPC-only; re-verified from two fresh angles that corroborate it more strongly than the original op-level probes:

  • Runtime DB schema (Runtime.dbo, the server's own source of truth): the SystemParameter table has no timezone parameter and no EventStorageMode (only EventStorageDuration / EventStorageLogPath). The server timezone exists only as per-block storage artifacts (HistoryBlock.TimeZoneOffset = e.g. 240 min, wwTimeZone = e.g. "Eastern Daylight Time") and a TimeZone reference/lookup table; StorageShard.TimeZoneId is NULL. So the timezone is a DST-specific, SQL-only, OS-derived value, not a clean server-config field exposed by any op.
  • Parameter-op probe (StringHandleProbeDiagnosticTests.TimezoneAndStorageMode_ParameterProbe): GetSystemParameter and GetRuntimeParameter (GETRP) were asked for every timezone candidate (TimeZone/ServerTimeZone/SystemTimeZone/TimeZoneName/SystemTimeZoneName/TimeStampRule/ ServerTime) and every storage-mode candidate (EventStorageMode/StorageMode/EventStorage/ EventStorageDuration). All returned null (GetSystemParameter) or threw ProtocolEvidenceMissingException (GETRP — non-string/empty response); only the HistorianVersion control returned a value (20,0,000,000). Note: TimeStampRule/EventStorageDuration do exist in the SystemParameter table yet GetSystemParameterAsync returns null for them — the shipped op only surfaces a whitelisted subset (a possible future widening, unrelated to R1.3/R1.4).

Conclusion: R1.3 GetServerTimeZoneAsync and R1.4 GetHistorianInfoAsync (EventStorageMode) are not deliverable as server ops on 2020. The only 2020 route to the timezone is a SQL read of HistoryBlock/TimeZone via ExecuteSqlCommand (R1.1) — a DST-specific value over a different mechanism than the roadmap's Status.GetSystemTimeZoneName. EventStorageMode has no 2020 representation at all (it is a 2023 R2 event-storage-architecture field). Deliver both only against a live 2023 R2 gRPC server.