C4: capture native event-send wire that populates Source_Object (CM_EVENT SourceName drop) #3

Closed
opened 2026-06-27 11:58:31 -04:00 by dohertj2 · 1 comment
Owner

Summary

The captured AddS2 / CM_EVENT event-send wire does not carry an event's source, so an ad-hoc
SendEvent lands in Runtime.dbo.Events with Source_Object = NULL (and the other
Source_*/Provider_* columns null). A source-filtered read of one's own just-sent event therefore
returns 0 rows. This issue tracks the reverse-engineering capture needed to close that.

Current behaviour

src/AVEVA.Historian.Client/Wcf/HistorianEventWriteProtocol.csSerializeEventValueBlob serializes,
in order: CM_EVENT tag GUID, EventTime FILETIME, OpcQuality ×2, the 0x118D value descriptor, the
event Id GUID, ReceivedTime FILETIME, Namespace, Type, struct-version, then string properties.
It never serializes evt.SourceName. The wire shape was decoded byte-for-byte from two native
captures (User.Write and Alarm.Set), and neither capture carried a source field — so there is
no evidence for which wire field (if any) maps to Source_Object.

Source_Object appears to be a Galaxy-platform association the App-Server alarm pipeline sets for
object-raised events, not something the generic CM_EVENT send path carries. That hypothesis is
unproven without a capture.

Root cause

No protocol evidence exists for the source field on the event-send wire. This is not fixable in the
managed client
by re-threading the value (the gateway already threads SourceName correctly all the
way to HistorianProtoMapper.ToSdk; the serializer drops it because no captured byte maps to it).

What's needed (reverse-engineering task)

  1. Produce a native event-send capture (instrument-wcf-writemessage) of an event that does
    populate Source_Object in dbo.Events — most likely an object-raised App-Server alarm event,
    contrasted against the existing User.Write / Alarm.Set generic sends that do not.
  2. Diff the two to isolate the wire field carrying the source.
  3. If a generic CM_EVENT send can populate it: extend SerializeEventValueBlob to emit that field,
    add a golden serializer test, and re-vendor.
  4. If only the platform alarm pipeline can populate it (i.e. the generic send genuinely cannot):
    document that as the closing finding and keep the managed path honest (no silent NULL source).

Acceptance criteria

  • A capture + decode doc under docs/reverse-engineering/ showing whether/which wire field maps to
    Source_Object.
  • Either (a) SerializeEventValueBlob emits the source field + golden test + a live
    SendEvent → source-filtered ReadEvents round-trip returning the event, or (b) a documented
    won't-fix with the protocol reason.

References

  • Serializer: src/AVEVA.Historian.Client/Wcf/HistorianEventWriteProtocol.cs (SerializeEventValueBlob).
  • Downstream tracking: HistorianGateway pending.md C4 (closed won't-fix at the gateway pending
    this capture); OtOpcUa follow-up plan FU-1; OtOpcUa live test Alarm_SendEvent_then_ReadEvents
    skips with this reason.
  • Sibling: this is the event-write analogue of the C2 event-read server-gate (histsdk PR #2 /
    #1) — both end at "needs evidence/behaviour not reconstructable from the current client."

https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii

## Summary The captured `AddS2` / CM_EVENT event-send wire does **not** carry an event's source, so an ad-hoc `SendEvent` lands in `Runtime.dbo.Events` with **`Source_Object = NULL`** (and the other `Source_*`/`Provider_*` columns null). A source-filtered read of one's own just-sent event therefore returns 0 rows. This issue tracks the **reverse-engineering capture** needed to close that. ## Current behaviour `src/AVEVA.Historian.Client/Wcf/HistorianEventWriteProtocol.cs` → `SerializeEventValueBlob` serializes, in order: CM_EVENT tag GUID, EventTime FILETIME, OpcQuality ×2, the `0x118D` value descriptor, the event Id GUID, ReceivedTime FILETIME, **Namespace**, **Type**, struct-version, then string properties. It **never serializes `evt.SourceName`**. The wire shape was decoded byte-for-byte from two native captures (`User.Write` and `Alarm.Set`), and **neither capture carried a source field** — so there is no evidence for which wire field (if any) maps to `Source_Object`. `Source_Object` appears to be a Galaxy-platform association the App-Server alarm pipeline sets for *object-raised* events, not something the generic CM_EVENT send path carries. That hypothesis is unproven without a capture. ## Root cause No protocol evidence exists for the source field on the event-send wire. This is **not fixable in the managed client** by re-threading the value (the gateway already threads `SourceName` correctly all the way to `HistorianProtoMapper.ToSdk`; the serializer drops it because no captured byte maps to it). ## What's needed (reverse-engineering task) 1. Produce a **native event-send capture (instrument-wcf-writemessage)** of an event that **does** populate `Source_Object` in `dbo.Events` — most likely an object-raised App-Server alarm event, contrasted against the existing `User.Write` / `Alarm.Set` generic sends that do not. 2. Diff the two to isolate the wire field carrying the source. 3. If a generic CM_EVENT send can populate it: extend `SerializeEventValueBlob` to emit that field, add a golden serializer test, and re-vendor. 4. If only the platform alarm pipeline can populate it (i.e. the generic send genuinely cannot): document that as the closing finding and keep the managed path honest (no silent NULL source). ## Acceptance criteria - A capture + decode doc under `docs/reverse-engineering/` showing whether/which wire field maps to `Source_Object`. - Either (a) `SerializeEventValueBlob` emits the source field + golden test + a live `SendEvent` → source-filtered `ReadEvents` round-trip returning the event, **or** (b) a documented won't-fix with the protocol reason. ## References - Serializer: `src/AVEVA.Historian.Client/Wcf/HistorianEventWriteProtocol.cs` (`SerializeEventValueBlob`). - Downstream tracking: HistorianGateway `pending.md` **C4** (closed won't-fix *at the gateway* pending this capture); OtOpcUa follow-up plan **FU-1**; OtOpcUa live test `Alarm_SendEvent_then_ReadEvents` skips with this reason. - Sibling: this is the event-**write** analogue of the C2 event-**read** server-gate (histsdk PR #2 / #1) — both end at "needs evidence/behaviour not reconstructable from the current client." https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Author
Owner

Resolved — via the gateway, no histsdk change needed

A live reverse-engineering probe (2026-06-27) overturned this issue's premise. Source_Object is
not a missing wire field — the historian derives it from an ordinary event property named
source_object
, the exact inverse of the read parser (HistorianEventRowProtocol.BuildEvent, which
hydrates SourceName from source_processvariable/source_object). The event-send serializer already
serializes string properties; the dedicated SourceName field is the only thing dropped.

Live proof (2023 R2 historian). Sending an event with Properties["source_object"] = <marker>
populates Runtime.dbo.Events.Source_Object = <marker> (reproduced). So a generic CM_EVENT send can
populate the column after all — the earlier "only the App-Server alarm pipeline sets it" belief was
wrong.

Fix — gateway-side, non-vendored. HistorianProtoMapper.ToSdk now threads a non-empty SourceName
into Properties["source_object"] (unless the caller already set that key). Verified by unit tests +
a live SendEvent → SQL round-trip asserting Source_Object == the sent SourceName. No change to
the vendored serializer and no new native capture was required — the wire was always capable.

Closing as resolved-via-gateway; no upstream histsdk work remains. (HistorianGateway pending.md C4
updated FIXED; unblocks OtOpcUa FU-1.)

https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii

## Resolved — via the gateway, **no histsdk change needed** A live reverse-engineering probe (2026-06-27) overturned this issue's premise. `Source_Object` is **not** a missing wire field — the historian derives it from an ordinary event **property named `source_object`**, the exact inverse of the read parser (`HistorianEventRowProtocol.BuildEvent`, which hydrates `SourceName` from `source_processvariable`/`source_object`). The event-send serializer already serializes string properties; the dedicated `SourceName` field is the only thing dropped. **Live proof (2023 R2 historian).** Sending an event with `Properties["source_object"] = <marker>` populates `Runtime.dbo.Events.Source_Object = <marker>` (reproduced). So a generic CM_EVENT send *can* populate the column after all — the earlier "only the App-Server alarm pipeline sets it" belief was wrong. **Fix — gateway-side, non-vendored.** `HistorianProtoMapper.ToSdk` now threads a non-empty `SourceName` into `Properties["source_object"]` (unless the caller already set that key). Verified by unit tests + a live `SendEvent` → SQL round-trip asserting `Source_Object` == the sent `SourceName`. No change to the vendored serializer and **no new native capture** was required — the wire was always capable. Closing as resolved-via-gateway; no upstream `histsdk` work remains. (HistorianGateway `pending.md` C4 updated FIXED; unblocks OtOpcUa FU-1.) https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/histsdk#3