Experimental Avro/Protobuf/Arrow reference encoders + PubSub adapters - #7
Closed
marcschier wants to merge 14 commits into
Closed
marcschier wants to merge 14 commits into
marcschier wants to merge 14 commits into
Conversation
…ters Adds two net8.0+ experimental libraries implementing the draft OPC UA DataEncoding extensions (opcua-drafts) as hand-rolled reference codecs plus PubSub NetworkMessage adapters, with NUnit round-trip tests. Libraries/Opc.Ua.Core.Experimental (IEncoder/IDecoder): - Avro: streaming Avro binary (zig-zag varint, LE floats, nullable unions, NodeId-by-parts); Variant/Action/Discovery support. SchemaId = CRC-64-AVRO Rabin fingerprint + single-object prefix. - Protobuf: hand-rolled protobuf wire; scoped to gRPC service messages. - Arrow: genuinely typed Apache Arrow (Apache.Arrow 18.1.0) - typed arrays, StructArray, ListArray, dense UnionArray; no JSON/blob carrier. Libraries/Opc.Ua.PubSub.Experimental: - Avro adapter drives the real AvroEncoder per field (Avro-in-Avro bytes). - Arrow adapter emits a real columnar RecordBatch (rows = DataSetMessages, typed per-field + header columns) per the Part 14 Arrow batch mapping. Build/scope notes: - net8.0;net9.0;net10.0 only (Apache.Arrow + net5+ BCL); RestrictForLegacyTfm no-ops the legacy CI CustomTestTarget matrix entries. - Round-trip-only conformance for this first reference; unsupported/out-of-scope decode paths fail loud (NotSupportedException), never corrupt. Includes fixes from code review: Avro present-empty-array terminator (fixed + regression test), Arrow dense-union declared type-ids made conformant, Arrow XmlElement decode implemented. Known limitation: Protobuf optional-presence reconstruction is unreliable for structures with 2+ optionals where an earlier one is absent (documented; not exercised by in-scope gRPC messages).
… no pragmas) Reflects the latest Part 14 spec state and applies a full quality pass to the experimental Avro/Protobuf/Arrow reference stack. Schema exchange (spec alignment): - Add AvroSchemaAnnouncement/Request and ArrowSchemaAnnouncement/Request message types matching the Part 14 §9 / §5.2 descriptors. - Add SchemaCache (thread-safe, announce-once per destination, SchemaId verified on ingest) + ISchemaResolver (pluggable cache-miss resolver), wired into the Avro and Arrow PubSub adapters (encoder announces on first SchemaId use; decoder resolves on miss). A resolver returning bytes inconsistent with the requested SchemaId is treated as "schema not available" so Try* decoders return null rather than throwing. Documented limitation: announcements currently carry an internal shape descriptor (change-tracking proxy), not the canonical Avro PCF / serialized Arrow Schema (cross-implementation interop is a follow-up). New round-trip + handshake tests. Quality hardening (all experimental files): - Add the long-form OPC Foundation MIT copyright header to every file. - Remove all blanket #pragma warning disable and fix the underlying issues (reformat dense one-liners to braced/<=120-col; fix CA and compiler nullable/obsolete warnings). Only 6 narrowly-scoped, justified suppressions remain (5x CA2000 Arrow array ownership handoff, 1x CS0618 raw byte[] body). - Document every public and internal symbol: <inheritdoc/> on IEncoder/IDecoder members (matching JsonEncoder), meaningful summaries elsewhere. Enable GenerateDocumentationFile on both libraries so CS1591 enforces documentation. Builds 0 warnings/0 errors on net8.0/net9.0/net10.0 (docs enforced); Core 21 and PubSub 8 tests pass; legacy-TFM RestrictForLegacyTfm no-op intact. No canonical encoding or SchemaId-algorithm changes.
marcschier
commented
Jul 6, 2026
…le, folders Addresses the maintainer's structural review of the experimental encoders: - Namespace: Opc.Ua.Core.Experimental -> Opc.Ua (matching the base encoders), Opc.Ua.PubSub.Experimental -> Opc.Ua.PubSub.Encoding, and the test projects -> Opc.Ua.Core.Tests / Opc.Ua.PubSub.Encoding.Tests. - Always use block method bodies: converted all expression-bodied members to braced bodies across the libraries and tests. - One class per file: split ArrowEncoder (ArrowEncoder/ArrowDecoder/Slot/A), AvroBinary (Reader/Writer), ProtobufWire (ProtoField/ProtoMessage/Proto), ProtobufGrpcMessages (one file per message), and SchemaCacheEntry. - Merged the Avro *.Variant partial classes into the concrete AvroEncoder/ AvroDecoder classes (single, non-partial, sealed) and deleted the partials. - Moved the PubSub Arrow* files into Encoding/Arrow/ (mirroring Encoding/Avro/). - Reorganized the tests into subfolders mirroring the source projects (Avro/Arrow/Protobuf/SchemaExchange, Encoding/Avro, Encoding/Arrow). Behavior, wire encodings, method signatures/semantics and the SchemaId algorithms are unchanged. Copyright headers, XML docs (CS1591 enforced via GenerateDocumentationFile) and the 6 narrowly-scoped justified pragmas are preserved. Builds 0 warnings/0 errors on net8.0/net9.0/net10.0; Core 21 and PubSub 8 tests pass; legacy-TFM no-op intact.
marcschier
commented
Jul 6, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix Variant array and matrix payload handling so containers are written as ArrayValue/MatrixValue messages instead of scalar Value bodies, and add Protobuf round-trip coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add scalar, array, and matrix branches for every OPC UA Variant body built-in supported by the canonical dense union layout. Add Arrow round-trip coverage across all body types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… allocation The buffered writer/reader introduced an 8 KB heap buffer per encoder/decoder instance, which regressed small-message allocation (Avro scalar encode went to ~9 KB/op). Rent the buffers from ArrayPool.Shared and return them from the codec Close() path via a Release() method (not IDisposable, to avoid pushing CA2000 disposal obligations onto every call site). Avro scalar allocation drops back to ~0.9 KB/op (below Binary) while keeping the large-payload batching win.
Adds ArrowIpcFraming {Stream, Batch}. In Batch mode the encoder emits a bare
Arrow RecordBatch message without the embedded Schema message (a fixed ~1.2 KB
per message), exposing the schema-message bytes/length so the schema can be
announced once out of band and resolved by SchemaId (the Arrow Flight model).
The decoder caches schemaId->schema (from a full stream or CacheSchema) and
reconstructs schema+batch+EOS to decode bare batches, reusing the existing
decode path. Adds bare-batch round-trip tests and an IPC framing-breakdown
measurement (schema vs batch bytes, stream vs bare-batch size per sample).
Addresses PR feedback that batch (which exchanges the schema out of band like Avro) should be the default. ArrowNetworkMessageEncoder.Framing now defaults to ArrowIpcFraming.Batch; tests that need a self-contained stream set Framing=Stream explicitly. Extends the Part 14 framing measurement to report full metrics (payload/encode/decode/alloc) for both stream and batch framings.
This was referenced Jul 10, 2026
Owner
Author
|
Superseded by three focused PRs that port each reference encoder into the production stack (next to Binary/Json/Xml/Uadp), removing the need for the
Recommended merge order: #11 (independent), then #10, then #12 (rebases onto |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two experimental class libraries to the stack that implement the draft OPC UA DataEncoding extensions (Avro binary, Protobuf, Apache Arrow) as hand-rolled reference encoder/decoders, plus PubSub NetworkMessage adapters, with NUnit round-trip tests. These are the C# reference implementation of the spec drafts in the
marcschier/opcua-draftsrepo.New projects
Libraries/Opc.Ua.Core.Experimental—IEncoder/IDecoderimplementations:SchemaId= CRC-64-AVRO (Rabin) fingerprint with Avro single-object prefix.StructArray,ListArray, denseUnionArray,FixedSizeBinaryArray. No JSON/base64/blob carrier (asserted by tests).Libraries/Opc.Ua.PubSub.Experimental—INetworkMessageEncoder/Decoder:AvroEncoderper field (self-describing Avro-in-Avrobytes).RecordBatch(rows = DataSetMessages, typed per-field + leading header columns; NetworkMessage headers in schema metadata) per the Part 14 Arrow batch mapping (ADBC/historian oriented).Apache.Arrow 18.1.0added to central package management, four projects added toUA.slnx.Target frameworks / CI
BitConverter.SingleToUInt32Bits,Convert.ToHexString,Stream.ReadExactly). They opt intoRestrictForLegacyTfmso the per-TFM CI matrix entries pinned to legacy TFMs (net472/net48/netstandard2.x) no-op them (verified locally).Opc.Ua.Core.Experimental.Tests17/17 andOpc.Ua.PubSub.Experimental.Tests3/3 pass on net8.0 and net10.0; legacy CustomTestTarget builds no-op cleanly.Conformance / scope
NotSupportedException) rather than corrupt.Code-review fixes included
Null. Fixed inAvroEncoder.WriteArray+ a dedicated multi-field regression test.XmlElementnow round-trips instead of being silently dropped.Known limitation
ProtobufDecoder).Companion to the encoding spec drafts in
marcschier/opcua-drafts(PR #4).Follow-up: closed encoder gaps, Avro perf, and performance numbers
InvalidCastExceptionfor matrix/array Variants). Round-trip tests added.Variant<Boolean>was unsupported). Round-trip tests added.ArrayPoolso small-message allocation is not penalised (Avro scalar encode alloc ~9.1 KB/op → ~0.9 KB/op, below Binary) while retaining the large-payload batching win.n/acells. Build 0/0 across net8/9/10; Core 118 + PubSub 8 tests + all round-trips green.core-specs/extras/performance/OPC-UA-Encoding-Performance-Comparison.md).Follow-up: Arrow bare-RecordBatch (
batch) framingAdds
ArrowIpcFraming {Stream, Batch}to the Arrow PubSub encoder.Batchemits a bare Arrow RecordBatch without the embedded Schema message (a fixed ~1.2 KB/message); the schema is announced once out of band and resolved by SchemaId (the Arrow Flight model). The decoder cachesschemaId -> schema(from a full stream orCacheSchema) and reconstructs the stream to decode bare batches. Measured savings: ~39% for a single sample, ~37% at 10 samples, shrinking to ~1.8% at 1000 (schema amortised). Bare-batch round-trip tests + an IPC framing-breakdown measurement added; build 0/0 all TFMs, PubSub tests 10/10.Update:
ArrowIpcFraming.Batchis now the encoder default (aligns with the spec default and the Avro schema-exchange model); the Part 14 harness measures both framings.