Ship tag extended-property reads over the 2020 WCF aa/Retr/GetTepByNm op: HistorianClient.GetTagExtendedPropertiesAsync(tag) -> name/value pairs. String-handle op reached with the Open2 storage-session GUID formatted uppercase (same format that unlocked GETRP/GETHI/ExeC). Routed via the name-based native path (GetTagExtendedPropertiesByName, server-fetch flag), not the index-based TagQuery path. Evidence-backed findings from the capture: - GetTepByNm (and GetTgByNm) succeed with the uppercase handle -- further validates the resolved string-handle wall. - QTB (StartTagQuery) does NOT punch through: captured uppercase, it still fails server-side (CMdServer::StartActiveTagnamesQuery over the aahMetadataServer pipe) -- a metadata-server blocker, not handle format. - R1.6 (localized properties) has NO distinct op (only error-message/UI-text localization in the managed client); collapses into R1.5. Closed, not throwing. Wire format (golden-pinned, synthetic bytes -- no dev tag names committed): - request tagNames = uint count + per-name(uint charCount + UTF-16) - response = uint tagCount + per-tag(marker + compact-ASCII name + uint propCount + per-prop(marker + compact-ASCII name + 0x43 VT_BSTR value) + trailer); sequence-paged. Adds: HistorianTagExtendedProperty model, HistorianTagExtendedPropertyProtocol (codec), HistorianWcfTagExtendedPropertyClient (orchestration), dialect + public API; golden WcfTagExtendedPropertyProtocolTests (4) + gated live test (HISTORIAN_TEP_TAG). Tooling: Capture-TagExtendedProperties.ps1, decode-tag-properties-capture.py, harness tag-extended-properties scenario. Docs: wcf-tag-extended-properties.md; roadmap R1.5 DONE / R1.6 collapsed; wall doc + memory updated with the QTB-server-side nuance. 228 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
4.9 KiB
Tag extended properties over 2020 WCF — GetTepByNm (HCAL R1.5)
Status: ✅ DONE + live-verified (2026-06-20). HistorianClient.GetTagExtendedPropertiesAsync(tag)
reads a tag's extended (user-defined) properties over the 2020 WCF op
aa/Retr/GetTagExtendedPropertiesFromName (GetTepByNm). Live-verified end-to-end from the
pure-managed .NET 10 client against the local 2020 Historian.
The op
GetTepByNm is on the Retrieval service (IRetrievalServiceContract4):
bool GetTagExtendedPropertiesFromName(
string handle, // Open2 storage-session GUID, UPPERCASE dash-no-braces
byte[] tagNames, // [MessageParameter pRequestBuff-style]
ref uint sequence, // paging cursor (0 on first call)
out byte[] tagExtendedProperties, // result buffer
out byte[] errorBuffer)
It is a string-handle op — reachable from the managed client because the handle is the Open2
storage-session GUID formatted storageSessionId.ToString("D").ToUpperInvariant() (the same handle
format that unlocked GETRP/GETHI/ExeC; see wcf-string-handle-wall.md). The Retrieval service
version handshake (Retr.GetV) is primed first, as the native client does.
Why the name-based path (not the TagQuery path)
There are two managed entry points:
- Index-based
TagQuery.GetTagExtendedPropertyInfo(start, count, …)— requires a priorStartTagQuery(QTB). On this 2020 box QTB fails server-side (error 1fromCMdServer::StartTagQuery::StartActiveTagnamesQueryover\\.\pipe\aahMetadataServer\console), so this path is dead here regardless of handle format. - Name-based
HistorianAccess.GetTagExtendedPropertiesByName(tagName, fetchFromServer, …)— issuesGetTepByNmdirectly with the tag name intagNames, no QTB needed. Its second arg forces a server fetch when true; when false the C++ client reads a local cache and returnserror 41 (Requested item not found)without any WCF round-trip. The SDK reproduces the name-based path.
Wire format (captured)
scripts/Capture-TagExtendedProperties.ps1 (NativeTraceHarness tag-extended-properties scenario +
instrument-wcf-{write,read}message) → decode with scripts/decode-tag-properties-capture.py.
Golden-pinned in WcfTagExtendedPropertyProtocolTests.
Request — tagNames buffer
uint32 count
per name: uint32 charCount + UTF-16LE chars
(For one tag: 01 00 00 00 + LL 00 00 00 + UTF-16 name.)
Response — tagExtendedProperties buffer
uint32 tagCount
per tag:
byte groupMarker (observed 0x01)
0x09 + uint16 byteLen + ASCII tagName (compact-ASCII string)
uint32 propertyCount
per property:
byte propMarker (observed 0x02 — likely the value type)
0x09 + uint16 byteLen + ASCII propertyName
0x43 + uint16 payloadLen + uint16 charCount + UTF-16LE value (VT_BSTR CRetVariant)
byte trailingMarker (observed 0x01)
payloadLen counts the charCount field (2 bytes) + the UTF-16 value bytes. Only the string value
variant (0x43) is evidence-backed; other variant types throw ProtocolEvidenceMissingException
(same discipline as GETRP). The single-byte 0x01/0x02/0x01 markers are pinned as observed
constants from a single capture; their full semantics are not independently disambiguated.
Example (sanitized — real capture used a dev tag/value):
tag "Reactor.Temp1" → property "Location" = "Plant/AreaA"
Paging
GetTepByNm is sequence-paged like GetNextQueryResultBuffer: call with sequence = 0, parse the
buffer, then re-call with the returned sequence. A small result returns everything on the first
call; the next call returns an empty/nil buffer (with a benign CClientUtil::FillBufferFromVector
terminator) — that is the stop signal. The SDK loops until the buffer carries no rows.
R1.6 (localized properties) — no distinct op on 2020
There is no GetTagLocalizedPropertiesFromName / GetTlpByNm op or
GetTagLocalizedPropertiesByName method in current/aahClientManaged.dll — the only "localized"
surfaces are ClientApp.GetLocalizedText and SMessageTextMap.GetLocalizedMessage (error-message /
UI-text localization), not tag properties. So R1.6 collapses into R1.5: extended properties
(GetTepByNm) are the user-defined tag-property read surface on 2020. R1.6 is closed as
"no separate op," not left throwing.
Shipped surface
HistorianClient.GetTagExtendedPropertiesAsync(tag)→IReadOnlyList<HistorianTagExtendedProperty>(Name/Valuepairs; empty when the tag has none).HistorianTagExtendedPropertyProtocol(serializer/parser),HistorianWcfTagExtendedPropertyClient(orchestration), goldenWcfTagExtendedPropertyProtocolTests, gated liveGetTagExtendedPropertiesAsync_AgainstLocalHistorian_ReturnsProperties(setHISTORIAN_TEP_TAGto a tag with extended properties).