5ce62a5900
ApplyScaling (HistorianTagDefinition.ApplyScaling): The EnsT2 trailer's second byte controls server-side scaling — `FE 00` mirrors MinRaw to MinEU and sets AnalogTag.Scaling=0; `FE 01` persists distinct MinRaw/MaxRaw and sets Scaling=1. Decoded by toggling set_ApplyScaling on the native harness and capturing the wire bytes for both values with identical inputs. The earlier docs claimed EnsureTagAsync needed a follow-up "UpdateTags" call; the WCF surface has no such operation — toggling that one byte is the whole fix. StorageRate (HistorianTagDefinition.StorageRateMs): Serializer accepts a non-default rate, validated empirically against the live server which only accepts quantized values (1000/5000/10000/60000/300000 ms). EnsureTagAsync upsert semantics: Second call on the same tag name with different fields succeeds and updates Description, MinEU, MaxEU, MinRaw, MaxRaw, Scaling in place (verified by direct SQL inspection in a live test). Plan + doc closeout: write-commands-reverse-engineering.md rewritten as a current-state plan with three workstreams (A doc closeout / B idempotency / C1 StorageRate) and a parallelism table; prior phase notes preserved as appendix. handoff.md, implementation-status.md, wcf-contract-evidence.md, README.md updated to remove "writes are out of scope" / non-existent UpdateTags references and document the actual EnsT2 wire format including the `FE xx` trailer. Reverse-engineering harness gains --write-apply-scaling and a SQL post-check that prints the persisted AnalogTag bounds so future RE sessions can verify wire→DB causality without leaving the harness. 169/169 tests pass (was 165; +4 new tests covering ApplyScaling, StorageRate golden bytes, StorageRate live persistence, and EnsureTagAsync upsert semantics). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
11 KiB
WCF Contract Evidence
Local run evidence
current\aahClient.dllexport inventory ran successfully. SHA256:77a778988e2d8f2d0e88113f8c8b0788a0ef34fa5134938a353976778144dc83.ArchestrA.HistorianAccess.OpenConnectionsucceeded againstlocalhost:32568usingHistorianConnectionArgswithConnectionType=ProcessandReadOnly=true.- Holding that native connection open produced established TCP sessions from the
native PowerShell process to
127.0.0.1:32568; the server-side listener was owned bySMSvcHost.exe, consistent with WCF Net.TCP port sharing. - The managed harness command
dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-probe localhost 32568successfully calledGetVthrough fully managed WCF/MDAS:net.tcp://localhost:32568/Histreturned version11net.tcp://localhost:32568/Retrreturned version4net.tcp://localhost:32568/Statreturned version0net.tcp://localhost:32568/Trxreturned version2net.tcp://localhost:32568/Storagedid not listen on this local installnet.tcp://localhost:32568/HistCertand/Hist-Integratedreset when called with the plain managedGetVcontract, while prefixed variants such as/HCAP/HistCertreturnedEndpointNotFoundSanitized output is stored indocs\reverse-engineering\wcf-probe-localhost.json.
netsh traceandpktmonproduced ETL files under%TEMP%\histsdk-captures, but their converted PCAPNG files contained zero packets. Built-in Windows packet capture is not sufficient for loopback evidence on this machine.- A dedicated managed certificate-binding probe now reaches
HistCert.GetVthrough MDAS over WCF Net.TCP transport security:net.tcp://localhost:32568/HistCertreturned version11net.tcp://10.100.0.48:32568/HistCertinitially failed endpoint identity validation because the server certificate presented DNS identitylocalhost- the same remote endpoint returned version
11when the client supplied endpoint DNS identitylocalhostSanitized outputs are stored indocs\reverse-engineering\wcf-cert-probe-localhost-latest.json,docs\reverse-engineering\wcf-cert-probe-remote-latest.json, anddocs\reverse-engineering\wcf-cert-probe-remote-localhost-identity-latest.json.
- The same remote server also accepts the plain managed WCF/MDAS probe on the
expected non-security-specific service paths:
net.tcp://10.100.0.48:32568/Histreturned version11net.tcp://10.100.0.48:32568/Retrreturned version4net.tcp://10.100.0.48:32568/Statreturned version0net.tcp://10.100.0.48:32568/Trxreturned version2Sanitized output is stored indocs\reverse-engineering\wcf-probe-remote-latest.json.
- Managed remote
Open2evidence matches localhost: integrated Windows auth succeeds onnet.tcp://10.100.0.48:32568/Hist-Integrated, while the same Windows transport binding fails on plain/Histbefore dispatch. The successful returned handle is accepted byRetr.IsOriginalAllowed. Session output bytes and transient handle values are redacted indocs\reverse-engineering\wcf-open2-remote-latest.json. - Managed remote
StartQuery2evidence is still negative but sharper: all 22 reconstructedDataQueryRequestvariants successfully open the integrated session and passRetr.IsOriginalAllowed, thenStartQuery2returnsfalsewith zero response and error sizes. The legacyStartQuerycall returns code238for each request and also returns zero response size/no response buffer. Sanitized request hashes are stored indocs\reverse-engineering\wcf-start-query-remote-latest.json. - A later bounded managed replay of the first byte-matched full-history
candidate used the same integrated open and
Retr.IsOriginalAllowedpath;StartQuery2still returnedfalsewith zero response/error sizes, while legacyStartQueryfaulted with a server null-reference. This keepsOpen2as useful connection evidence, but not as a viable replacement for the nativeOpenConnection3session state required by query reads. - Managed wildcard tag browse remains positive evidence for an
Open2-backed operation:Retr.StartLikeTagNameSearchreturned0, and oneGetLikeTagnamesbatch returned the deterministic 66-byte single-tag buffer with SHA-2562d450a55f392aed0026e9a957fefa3b116aab6ec81912c5d824c6b9a1ff5a4a1. - Managed remote scalar tag calls also accept the integrated session handle:
Retr.GetTagTypeFromNamereturns code0and tag type1forOtOpcUaParityTest_001.Counter;Retr.IsManualTagreturns code0andfalse; legacyRetr.GetTagInfoFromNamereturns238with zero metadata bytes. FiveGetTgByNmtag-name buffer variants and fiveGetTgtag-id buffer variants all return238, sequence0, and zero output bytes. This suggests the calls are reaching server logic but the metadata-returning contract shape or request buffer is still incomplete. Sanitized output is stored indocs\reverse-engineering\wcf-tag-info-remote-latest.json.
Decompiled service contracts
current\aahClientManaged.dll contains WCF contracts with namespace aa:
HistoryServiceContract.IHistoryServiceContract[ServiceContract(Name = "Hist", Namespace = "aa")]GetInterfaceVersionas operationGetVOpenConnectionas operationOpenCloseConnectionas operationCloseValidateClientas operationVldCUpdateClientStatusas operationUpdCAddTagsasAddT,RegisterTagsasRTagAddStreamValuesasAddS,SetClientTimeOutasSetT
HistoryServiceContract.IHistoryServiceContract2[ServiceContract(Name = "Hist", Namespace = "aa")]- byte-buffer session open uses
OpenConnection2as operationOpen2 - extended client status uses
UpdC2/UpdC3 - extended write and maintenance calls include
EnsT,DelT,AddS2,ExKey,ValCl, andGetI
RetrievalServiceContract.IRetrievalServiceContract[ServiceContract(Name = "Retr", Namespace = "aa")]StartQuery,GetNextQueryResultBuffer,EndQueryuse default operation names- tag type/name helpers and tag info calls use default operation names
RetrievalServiceContract.IRetrievalServiceContract2/3/4- extended bool/error-buffer variants
- SQL/recordset byte stream calls
- tag query calls
QTB,QTG,QTE - event query calls use default operation names
- extended property calls include
GetTgByNm2andGetTepByNm
StorageServiceContract.IStorageServiceContract[ServiceContract(Name = "Storage", Namespace = "aa")]- storage/session, metadata, streamed-value, block, snapshot, and delete-tag calls
StatusServiceContract.IStatusServiceContract[ServiceContract(Name = "Stat", Namespace = "aa")]GetInterfaceVersionasGetV- server time, timezone, DB case sensitivity, and logging use default operation names
StatusServiceContract.IStatusServiceContract2- extended status operations include
GetSystemParameter,GetTimeZoneNames, license checks, historian info, and process/ping helpers - ping and historian-info helpers use aliases
PNGS,PNGP, andGETHI
- extended status operations include
TransactionServiceContract.ITransactionServiceContract[ServiceContract(Name = "Trx", Namespace = "aa")]- snapshot forwarding and non-streamed value transactions
aahMDASEncoder.ClientMessageEncoder wraps an inner WCF encoder and exposes
media/content type application/x-mdas. This means the first managed driver
transport target should be WCF Net.TCP plus the MDAS content-type encoder, not
the earlier speculative raw-frame layer.
Current unknowns
- Endpoint URI paths
net.tcp://{host}:32568/Hist,/Retr,/Stat, and/Trxare confirmed forGetVcalls on the local 2020 install. - Relay and local WCF probe evidence also identify security-specific history
endpoints
/HistCertand/Hist-Integrated./HistCertis confirmed as aHistcontract endpoint when called with MDAS over TLS transport security;/Hist-Integratedremains the Windows negotiate endpoint for integrated session open. - Managed
Open2evidence confirms/Hist-Integratedis the correct endpoint for integrated Windows auth. The plain/Histendpoint rejects the Windows transport-security upgrade before dispatching the operation. - The storage contract is confirmed statically, but
/Storagewas not a listening endpoint in the local probe; storage may be routed through session-specific storage/shard endpoints. Hist.OpenConnectionreaches server logic, but the native password/session packet encoding is not decoded yet. Seewcf-open-localhost.md.Hist.Open2is confirmed reachable with a managed version-1 byte buffer. Empty credentials return custom native error171(AuthenticationFailed), and the harness decodes observed five-byte error buffers as type plus little-endian error code. This confirms framing has progressed past packet-version rejection. Seewcf-open2-localhost.md.Statis reachable, butCStatusConnectionWCF.GetServerTimeis a no-op stub in the decompiled native WCF path and handle-dependent status calls fail with handle0. Seewcf-status-localhost.md.- Query request and response byte-buffer layouts are still proprietary payloads
inside WCF operations such as
StartQueryandGetNextQueryResultBuffer. - Write payload layouts decoded for the two supported ops:
Hist.EnsT2(analog)144-byteCTagMetadataInBuffpayload — leading0x4Emarker, fixed 10-byte signature, 1-byte CDataType discriminator (0x01Float /0x21Double /0x09UInt2 /0x11UInt4 /0x29Int2 /0x31Int4), 16 zero placeholder bytes, compact-ASCII tag name, 16 bytes of0xFF, compact-ASCII description, compact-ASCIIMDAS, 7-byte flag block, uint32 storage rate, int64 FILETIME, scaling block (compact1A 03for default 0/100/0/100 ranges OR1F 00+ 4 doubles MinEU/MaxEU/MinRaw/MaxRaw for explicit), compact-ASCII engineering unit, uint320x2710constant, double 1.0 (IntegralDivisor), 2-byte trailerFE xxwherexxis the ApplyScaling flag (0x00false /0x01true). Live-verified: with0x01the server persists distinct MinRaw/MaxRaw and setsAnalogTag.Scaling=1; with0x00it mirrors MinRaw to MinEU. Captured fixtures live atartifacts/reverse-engineering/instrumented-wcf-writemessage-writes/(default ranges) andartifacts/reverse-engineering/apply-scaling-experiment/(both ApplyScaling values for the same input ranges). Connection mode is0x401(Process | Write | IntegratedSecurity) — the read-mode0x402makes the server return err 132 silently.Hist.DelTtagNamesbyte buffer —ushort 0x6751,ushort 1,uint32 tagCount, then per taguint32 charCount + UTF-16-LE chars. Decoded via wire capture against the sandbox tag.Hist.AddS2(write samples) is architecturally blocked — server runtime cache requires IOServer / Application Server pipeline registration, not just aTagrow inRuntime.dbo. Three reproduction attempts (real wwTagKey, fresh session, 8s settle wait) confirmed129 "Tag not found in cache"is the gate. No AddS2 wire bytes leave the client.