Files
histsdk/docs/plans/r1.8-r1.9-summary-queries.md
T
Joseph Doherty 085f01123c docs: scope R1.8/R1.9 summary queries (decode targets located)
Established that analog/state-summary queries are reachable on 2020 WCF — they
ride the proven uint-handle StartQuery2 path, and the request serializer already
carries QueryType/SummaryType/ColumnSelectorFlags. Located every decode target in
aahClientManaged.dll:

- CAnalogSummaryValue.UnpackFromValueBuffer (0x06000394) — row decoder
- CAnalogSummaryValue/Struct fields — Min/Max/First/Last/ValueCount/TimeGood/
  Integral/IntegralOfSquares (+ per-field DateTimes, LinearIntegral)
- CStateSummaryStruct — MinContained/MaxContained/TotalContained/PartialStart/
  PartialEnd/StateEntryCount
- QueryColumnSelector.Select{Analog,State,NonSummary}Columns — column flags
- INSQL_QUERYTYPE / HISTORIAN_SUMMARYTYPE — the query/summary enum values

UnpackFromValueBuffer is reader-call-based (no literal offsets), so a correct
parser needs a captured real buffer. Per project discipline no guessed summary
code was added to src/. New plan doc lays out the recover-params -> live-capture
-> decode -> implement+test path. Roadmap R1.8/R1.9 marked scoped/ready.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 15:53:59 -04:00

67 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# R1.8 / R1.9 — Analog-summary & State-summary queries (implementation plan)
**Status (2026-06-20): scoped + de-risked. Reachable on 2020 WCF. Decode targets located in
the native dll. Ready to implement; not yet started in `src/` (no guessed code shipped).**
Unlike the M1 *read* items gated by the [string-handle wall](../reverse-engineering/wcf-string-handle-wall.md),
summary queries ride the **proven `uint`-handle `StartQuery2`** path — the same call the working
raw/aggregate reads use. So they are genuinely reachable here; the only work is (a) the right
request parameters and (b) decoding the summary row buffer.
## What's already in place
`HistorianDataQueryRequest` + `SerializeFullHistoryRequest`
(`Wcf/HistorianDataQueryProtocol.cs`) already serialize every field a summary query needs:
`QueryType` (INSQL_QUERYTYPE), `SummaryType` (HISTORIAN_SUMMARYTYPE), `AggregationType`,
`ColumnSelectorFlags`, `Resolution`. Normal reads send `SummaryType=0` and
`ColumnSelectorFlags=0x0000_8182_0007_82FF`. A summary query is the **same request with summary
values in those three fields**, then a different row parser on the result buffer.
## Decode targets recovered from `current/aahClientManaged.dll`
Found via `methods … Summary` + `dnlib-method`:
| Native artifact | Token | Use |
|---|---|---|
| `CAnalogSummaryValue.UnpackFromValueBuffer` | `0x06000394` | **the analog-summary row decoder** — a chain of buffer-reader calls (not literal offsets), so decode empirically against a captured buffer |
| `CAnalogSummaryValue.PackToVtq` | `0x06000395` | inverse (for a future write path) |
| `CAnalogSummaryValue` setters | `0x0600038A92` | wire field set: **StartDateTime, Min, Max, First, Last, ValueCount, TimeGood, Integral, IntegralOfSquares** |
| `CAnalogSummaryStruct` setters | `0x0600036977` | fuller field set: adds **MinDateTime, MaxDateTime, FirstDateTime, LastDateTime, FirstNullDateTime, LastNullFlag, LinearIntegral** |
| `CStateSummaryStruct` setters | `0x0600039BA0` | **state-summary fields: MinContained, MaxContained, TotalContained, PartialStart, PartialEnd, StateEntryCount** |
| `QueryColumnSelector.SelectAnalogSummaryColumns` | `0x0600004B` | builds `ColumnSelectorFlags` for analog summary via `CColumnNameMap.GetColumnFlag(name)` per column |
| `QueryColumnSelector.SelectStateSummaryColumns` | `0x0600004C` | same, state summary |
| `QueryColumnSelector.SelectNonSummaryColumns` | `0x0600004D` | the default (matches the `0x…82FF` flags reads already send) |
| `CTypeMetadata.IsAnalogSummary` / `IsStateSummary` | `0x060001A4/A5` | server-side type gating |
| `INSQL_QUERYTYPE` / `HISTORIAN_SUMMARYTYPE` | enums `0200013F` / `02000191` | the `QueryType` / `SummaryType` values to send |
## Open questions (nail these next, in order)
1. **Request params.** Recover the exact `QueryType` + `SummaryType` (+ whether `ColumnSelectorFlags`
must change) for analog vs state summary. Source: decompile `INSQL_QUERYTYPE`/`HISTORIAN_SUMMARYTYPE`
enum members and `Select{Analog,State}SummaryColumns` (the `GetColumnFlag` column-name `ldstr`
operands → the flag set). The standard QueryType map (Cyclic=0 … EndBound=14) is already verified;
summary is expected to be a `SummaryType`≠0 with an existing base `QueryType`, **not** a new mode
ordinal — confirm.
2. **Row layout.** Capture a real `GetNextQueryResultBuffer2` buffer for an analog summary of a
data-bearing tag (`SysTimeSec`) over a multi-hour window with an interval, then decode against the
`CAnalogSummaryValue` field set. Likely each row = StartDateTime FILETIME + the 8 typed fields.
## Implementation steps (per the project's two-tests discipline)
1. Add request params to `HistorianDataQueryRequest` builders (a `BuildAnalogSummaryRequest` /
`BuildStateSummaryRequest` alongside `BuildAggregateQueryRequest`).
2. **Live-probe** `SysTimeSec` via a gated diagnostic; sanitize the response into
`fixtures/protocol/analog-summary/` using the CW-1 pipeline.
3. Write `TryParseGetNextQueryResultBufferAnalogSummaryRows` (+ state variant) against the fixture.
4. Public API: `ReadAnalogSummaryAsync` / `ReadStateSummaryAsync` returning new models
`HistorianAnalogSummary` (Min/Max/First/Last/Avg=Integral÷TimeGood/ValueCount/…) and
`HistorianStateSummary` (per-state contained/partial/entry-count). Reuse `RunQuery` plumbing.
5. Golden-byte test on the parser + gated live test on `localhost` (assert non-empty, fields sane).
## Why stop here this session
`UnpackFromValueBuffer` is reader-call-based (no literal offset table), so a correct parser needs a
**captured real buffer** to decode against — that's the next concrete action, not a guess. Per
project rule ("never guess wire bytes; leave throwing until evidence supports it") no summary code
was added to `src/` yet. Everything above is the evidence needed to implement directly.