feat(grpc-events): v8 OpenConnection serializer + native error decode (Path A disproven)

Builds the native 2023 R2 version-8 OpenConnection format, which (unlike v6)
carries a ConnectionType byte (Event vs Process) — required because the 2023 R2
server returns event rows only on an Event connection.

- HistorianOpen2Protocol.SerializeNativeOpenConnectionVersion8: reproduces the
  302-byte v8 layout decoded from a live capture (version 8, markers, client-key
  GUID, username HString, length-prefixed credential token, ClientType /
  ConnectionType / flag / constant word / compact metadata / two empty strings;
  the tail reuses WriteClientCommonInfo). Golden-tested
  (Version8EventSerializerReproducesCapturedNativeStructure).
- HistorianNativeHandshake.BuildEventOpenConnectionVersion8Request: ConnectionType=
  Event, zeroed credential token (mirroring how v6 zeros its credential block and
  relies on the separate ValidateClientCredential handshake).
- HistorianGrpcHandshake.OpenSession: optional eventConnection switch; the
  OpenConnection failure path now decodes the native error (type/code/ASCII).

Path A (reuse ValidateClientCredential + zeroed token) was live-tested and
DISPROVEN: the server parses the v8 buffer but rejects it at the auth check with
native 132/34 "EstablishConnection Failed to get client key" — the v8 path looks
up the client key in the registry HistoryService.ExchangeKey (ECDH) populates,
not the one ValidateClientCredential does. The event orchestrator is therefore
reverted to the v6 session (gated test still pins the no-row throw). The v8
serializer/builder are retained for Path B (implement ExchangeKey). 323/323
offline tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
Joseph Doherty
2026-06-23 09:45:52 -04:00
parent ea85ea248d
commit 0b1e9d0a7f
5 changed files with 149 additions and 4 deletions
@@ -138,6 +138,11 @@ internal sealed class HistorianGrpcEventOrchestrator
private List<HistorianEvent> RunEventChain(DateTime startUtc, DateTime endUtc, HistorianEventFilter? filter, CancellationToken cancellationToken)
{
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
// NOTE: the event read needs an Event-type (v8) connection, but the v8 OpenConnection is
// server-coupled to HistoryService.ExchangeKey (ECDH) — it rejects a v8 open made on our
// ValidateClientCredential session with native 132/34 "EstablishConnection Failed to get client
// key" (Path A, disproven 2026-06-23). Staying on the v6 session until ExchangeKey is
// implemented (Path B). The v8 serializer + BuildEventOpenConnectionVersion8Request are ready.
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken);
RegisterCmEventTag(connection, session, cancellationToken);