Skip to content

Add high-performance PubSub transcoders (subscriber-to-publisher)#3956

Open
marcschier wants to merge 4 commits into
OPCFoundation:masterfrom
marcschier:pubsub-transcoders
Open

Add high-performance PubSub transcoders (subscriber-to-publisher)#3956
marcschier wants to merge 4 commits into
OPCFoundation:masterfrom
marcschier:pubsub-transcoders

Conversation

@marcschier

Copy link
Copy Markdown
Collaborator

Description

Adds high-performance PubSub transcoders to the Opc.Ua.PubSub library (new Opc.Ua.PubSub.Transcoding namespace) that take subscriber-received NetworkMessages and transcode them for re-publishing on a publisher connection, supporting all transformations.

The transcoder operates on the decoded PubSubNetworkMessage intermediate representation — the seam between the pluggable INetworkMessageDecoder/INetworkMessageEncoder — so cross-encoding and structural transforms happen without a full dataset re-sampling round-trip. Pipeline per message: decode(+unwrap) → transforms → project-to-target-profile → encode(+wrap), with a raw-frame zero-copy passthrough fast path for identity same-encoding routes.

What's included

  • Engine: PubSubTranscoder (frame-level ITranscoder) + NetworkMessageTranscoder (structured primitive), TranscodeSpec/TranscodeInput/TranscodeResult/TranscodeContext.
  • Cross-encoding: NetworkMessageProfileProjector maps UADP↔JSON (all four source/target combinations), translating content masks, DataSetMessage subtypes, identifiers, timestamps, and metadata.
  • 8 built-in transforms: identifier remap (PublisherId/WriterGroupId/DataSetWriterId/DataSetClassId), field-encoding conversion (Variant/RawData/DataValue), field projection (select/drop/reorder), field rename, per-field value transform, message-type filtering (KeyFrame/DeltaFrame/Event/KeepAlive), metadata rewrite, and a delegate escape hatch.
  • Transcoder-managed security: TranscodeSecurity re-secures via the existing UADP UadpSecurityWrapper; an AllowInsecureCrossEncoding policy (default off) refuses silent security downgrades, since message-layer security in Part 14 is UADP-only (secured→JSON relies on transport-layer security).
  • In-process bridge: an opt-in IReceivedNetworkMessageSink receive hook on PubSubConnection, PubSubTranscodingBridge, and ConnectionTranscodeEgress (chunk/MaxNetworkMessageSize-aware send).
  • Fluent / DI: IPubSubBuilder.AddTranscodingBridge(...) + PubSubTranscoderBuilder; the bridge is started as a hosted service after the PubSub application and resolves source/target connections by name.

Design notes

  • Additive only — no public API is obsoleted and no behavior changes when the feature is unused (the receive-loop observer is null-guarded). One private method (PubSubConnection.SendChunkedAsync) was refactored to be identifier-based so the egress can reuse chunking; its reflection-based unit test was updated accordingly.
  • Reuses UadpEncoder.EncodeWithSecurityBoundary and UadpDecoder.TryReadOuterPrefix rather than duplicating the fragile UADP security-splitting logic.
  • New public API follows repo conventions (ArrayOf<T>, Variant, spans/ByteString, sealed-by-default, async-only, AOT-clean, license headers, .editorconfig).

Testing & docs

  • 33 unit tests (each transform, projector for all four combos, engine fast-path/round-trip/drop/no-encoder, security downgrade policy, fluent builder) + 4 end-to-end integration tests driving a real PubSubConnection receive path through the bridge and egress.
  • TranscodingBenchmarks (UADP↔JSON, identity fast path, projection).
  • New Docs/PubSubTranscoding.md with links from Docs/README.md and a Transcoding section in Docs/PubSub.md.

Verified locally: the Opc.Ua.PubSub library builds 0 warnings/errors on all target frameworks (net472/net48/netstandard2.1/net8.0/net9.0/net10.0); the transcoding tests pass on net10.0 and net48; the full Opc.Ua.PubSub.Tests suite passes (1167 passed, 1 skipped, 0 failed) with no regressions.

Related Issues

No tracking issue yet — new feature. Happy to open one to host the ADR discussion if preferred.

Checklist

Put an x in the boxes that apply. You can complete these step by step after opening the PR.

  • I have signed the CLA and read the CONTRIBUTING doc.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added all necessary documentation.
  • I have verified that my changes do not introduce (new) build or analyzer warnings.
  • I ran all tests locally using the UA.slnx solution against at least .net framework and .net 10, and all passed. (Ran the full Opc.Ua.PubSub.Tests suite on net10.0 and the transcoding tests on net48; full-solution run pending.)
  • I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings.
  • I have addressed all PR feedback received.

Add Opc.Ua.PubSub.Transcoding: transcode subscriber-received NetworkMessages
to a publisher side with UADP<->JSON cross-encoding, an ordered transform
pipeline (id remap, field encoding, projection, rename, value, message-type,
metadata), transcoder-managed re-securing (AllowInsecureCrossEncoding policy),
a raw-frame zero-copy passthrough fast path, an opt-in receive hook on
PubSubConnection, and a fluent AddTranscodingBridge DI surface. Adds unit +
integration tests, benchmarks, and Docs/PubSubTranscoding.md.
Comment thread Docs/PubSubTranscoding.md Outdated
@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.91549% with 100 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.58%. Comparing base (2b4b067) to head (393921b).

Files with missing lines Patch % Lines
...bSub/Transcoding/NetworkMessageProfileProjector.cs 81.95% 13 Missing and 11 partials ⚠️
...ries/Opc.Ua.PubSub/Transcoding/PubSubTranscoder.cs 81.15% 6 Missing and 7 partials ⚠️
...ries/Opc.Ua.PubSub/Connections/PubSubConnection.cs 85.54% 8 Missing and 4 partials ⚠️
....Ua.PubSub/Transcoding/NetworkMessageTranscoder.cs 65.00% 3 Missing and 4 partials ⚠️
...ries/Opc.Ua.PubSub/Transcoding/IdRemapTransform.cs 81.25% 2 Missing and 4 partials ⚠️
...c.Ua.PubSub/Transcoding/PubSubTranscodingBridge.cs 87.50% 4 Missing and 2 partials ⚠️
.../Opc.Ua.PubSub/Transcoding/FieldRenameTransform.cs 76.19% 2 Missing and 3 partials ⚠️
.../Opc.Ua.PubSub/Transcoding/MessageTypeTransform.cs 72.22% 2 Missing and 3 partials ⚠️
...ranscoding/PubSubTranscodingBridgeHostedService.cs 92.72% 0 Missing and 4 partials ⚠️
...raries/Opc.Ua.PubSub/Transcoding/ValueTransform.cs 80.00% 2 Missing and 2 partials ⚠️
... and 6 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3956      +/-   ##
==========================================
+ Coverage   73.51%   73.58%   +0.07%     
==========================================
  Files        1170     1192      +22     
  Lines      170175   170883     +708     
  Branches    29363    29513     +150     
==========================================
+ Hits       125097   125743     +646     
- Misses      34072    34090      +18     
- Partials    11006    11050      +44     
Files with missing lines Coverage Δ
...ncyInjection/PubSubTranscodingBuilderExtensions.cs 100.00% <100.00%> (ø)
...c.Ua.PubSub/Transcoding/PubSubTranscoderBuilder.cs 100.00% <100.00%> (ø)
...ries/Opc.Ua.PubSub/Transcoding/TranscodeContext.cs 100.00% <100.00%> (ø)
...aries/Opc.Ua.PubSub/Transcoding/TranscodeResult.cs 100.00% <100.00%> (ø)
...ies/Opc.Ua.PubSub/Transcoding/TranscodeSecurity.cs 100.00% <100.00%> (ø)
...braries/Opc.Ua.PubSub/Transcoding/TranscodeSpec.cs 100.00% <100.00%> (ø)
...pc.Ua.PubSub/Transcoding/TranscodeTargetOptions.cs 100.00% <100.00%> (ø)
...Ua.PubSub/Transcoding/ConnectionTranscodeEgress.cs 88.23% <88.23%> (ø)
....Ua.PubSub/Transcoding/DelegateMessageTransform.cs 77.77% <77.77%> (ø)
...ies/Opc.Ua.PubSub/Transcoding/MetaDataTransform.cs 77.77% <77.77%> (ø)
... and 13 more

... and 20 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@marcschier marcschier marked this pull request as ready for review July 4, 2026 15:59
Copilot AI review requested due to automatic review settings July 4, 2026 15:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds a new Opc.Ua.PubSub.Transcoding feature set enabling high-performance subscriber-to-publisher PubSub message transcoding (UADP↔JSON), including transform pipelines, optional receive hooks, and egress support, with accompanying tests and documentation.

Changes:

  • Introduces new transcoding public APIs (spec/context/input/result, projector/transcoder engine, built-in transforms, bridge + DI hosted service).
  • Extends PubSubConnection / IPubSubConnection with an opt-in receive-path sink registration and a target-connection egress send helper (incl. chunking reuse).
  • Adds unit/integration tests, micro-benchmarks, and documentation for the transcoding feature.

Reviewed changes

Copilot reviewed 43 out of 43 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
Tests/Opc.Ua.PubSub.Tests/Transcoding/TranscodingTestUtilities.cs Adds shared helpers for transcoding unit/integration tests (context, encoders, sample messages, decode helpers).
Tests/Opc.Ua.PubSub.Tests/Transcoding/TranscodeTransformTests.cs Adds unit tests for built-in message transforms.
Tests/Opc.Ua.PubSub.Tests/Transcoding/TranscodeSecurityAndBuilderTests.cs Adds unit tests for TranscodeSecurity and fluent builder behavior.
Tests/Opc.Ua.PubSub.Tests/Transcoding/PubSubTranscodingBridgeIntegrationTests.cs Adds end-to-end integration tests for receive-hook → transcode → egress pipeline.
Tests/Opc.Ua.PubSub.Tests/Transcoding/PubSubTranscoderTests.cs Adds unit tests for frame-level transcoder behavior (fast path, cross-encoding, security policy, missing encoder).
Tests/Opc.Ua.PubSub.Tests/Transcoding/NetworkMessageProfileProjectorTests.cs Adds unit tests for projector across encoding combinations and target options.
Tests/Opc.Ua.PubSub.Tests/Connections/PubSubConnectionPrivateMethodTests.cs Updates reflection-based test after SendChunkedAsync signature refactor.
Tests/Opc.Ua.PubSub.Tests/Benchmarks/TranscodingBenchmarks.cs Adds micro-benchmarks for transcoding paths (UADP↔JSON, identity fast path, projection).
Libraries/Opc.Ua.PubSub/Transcoding/ValueTransform.cs Adds per-field value transform implementation.
Libraries/Opc.Ua.PubSub/Transcoding/TranscodeTargetOptions.cs Adds target format options (field encoding override, JSON single-message mode, metadata version preservation).
Libraries/Opc.Ua.PubSub/Transcoding/TranscodeSpec.cs Adds declarative transcode specification and identity detection.
Libraries/Opc.Ua.PubSub/Transcoding/TranscodeSecurity.cs Adds security policy and UADP re-wrapping support with downgrade refusal logic.
Libraries/Opc.Ua.PubSub/Transcoding/TranscodeResult.cs Adds structured output for frames/messages + fast-path indicator.
Libraries/Opc.Ua.PubSub/Transcoding/TranscodeInput.cs Adds input record including decoded message + optional wire frame for fast path.
Libraries/Opc.Ua.PubSub/Transcoding/TranscodeEncoding.cs Adds encoding family enum + helper conversions/classification.
Libraries/Opc.Ua.PubSub/Transcoding/TranscodeContext.cs Adds shared per-run context (encoding context + telemetry + metadata registry access).
Libraries/Opc.Ua.PubSub/Transcoding/ReceivedNetworkMessage.cs Adds received-message envelope for receive-path sinks.
Libraries/Opc.Ua.PubSub/Transcoding/PubSubTranscodingBridgeHostedService.cs Adds DI/hosting integration to resolve connections and start a bridge.
Libraries/Opc.Ua.PubSub/Transcoding/PubSubTranscodingBridge.cs Adds in-process bridge implementing receive-path sink and forwarding to egress.
Libraries/Opc.Ua.PubSub/Transcoding/PubSubTranscoderBuilder.cs Adds fluent builder for bridge/spec configuration + internal descriptor.
Libraries/Opc.Ua.PubSub/Transcoding/PubSubTranscoder.cs Adds frame-level transcoder (fast path, structured transcode, encode + UADP re-securing).
Libraries/Opc.Ua.PubSub/Transcoding/NetworkMessageTranscoder.cs Adds structured transcoder applying transforms + projector.
Libraries/Opc.Ua.PubSub/Transcoding/NetworkMessageProfileProjector.cs Adds UADP↔JSON projector and same-encoding option application.
Libraries/Opc.Ua.PubSub/Transcoding/MetaDataTransform.cs Adds transform for metadata-announcement message rewriting.
Libraries/Opc.Ua.PubSub/Transcoding/MessageTypeTransform.cs Adds message-type filter/relabel transform.
Libraries/Opc.Ua.PubSub/Transcoding/IdRemapTransform.cs Adds identifier remap transform (publisher/group/class/writer ids).
Libraries/Opc.Ua.PubSub/Transcoding/ITranscoder.cs Adds frame-level transcoder interface.
Libraries/Opc.Ua.PubSub/Transcoding/IReceivedNetworkMessageSink.cs Adds receive-path sink interface.
Libraries/Opc.Ua.PubSub/Transcoding/IPubSubTranscodeEgress.cs Adds egress abstraction for sending transcoded frames.
Libraries/Opc.Ua.PubSub/Transcoding/IPubSubMessageTransform.cs Adds transform interface for message-level pipeline steps.
Libraries/Opc.Ua.PubSub/Transcoding/INetworkMessageTranscoder.cs Adds interface for structured transcoding primitive.
Libraries/Opc.Ua.PubSub/Transcoding/INetworkMessageProfileProjector.cs Adds interface for profile projection (UADP/JSON concrete rematerialization).
Libraries/Opc.Ua.PubSub/Transcoding/FieldRenameTransform.cs Adds field rename transform.
Libraries/Opc.Ua.PubSub/Transcoding/FieldProjectionTransform.cs Adds include/exclude field projection transform.
Libraries/Opc.Ua.PubSub/Transcoding/FieldEncodingTransform.cs Adds field-encoding conversion transform.
Libraries/Opc.Ua.PubSub/Transcoding/DelegateMessageTransform.cs Adds delegate-backed transform escape hatch.
Libraries/Opc.Ua.PubSub/Transcoding/ConnectionTranscodeEgress.cs Adds default-connection egress that sends frames (and relies on connection chunking helper).
Libraries/Opc.Ua.PubSub/DependencyInjection/PubSubTranscodingBuilderExtensions.cs Adds IPubSubBuilder.AddTranscodingBridge(...) extension to register hosted service.
Libraries/Opc.Ua.PubSub/Connections/PubSubConnection.cs Adds receive-sink registration + notification; refactors chunking helper signature; adds SendTranscodedFrameAsync.
Libraries/Opc.Ua.PubSub/Connections/IPubSubConnection.cs Adds RegisterReceivedNetworkMessageSink(...) API.
Docs/README.md Links new transcoding documentation.
Docs/PubSubTranscoding.md Adds detailed feature guide for transcoding architecture/configuration/security/bridge/DI.
Docs/PubSub.md Adds transcoding section and links to the new guide.
Comments suppressed due to low confidence (1)

Libraries/Opc.Ua.PubSub/Transcoding/TranscodeSpec.cs:1

  • TranscodeSpec.IsIdentity ignores TargetOptions.PreserveMetaDataVersion. If PreserveMetaDataVersion is set to false on a same-encoding route, the frame-level transcoder may incorrectly take the raw-frame fast path and skip the requested format change. Include TargetOptions.PreserveMetaDataVersion in the identity check (and consider comparing all current options) so the fast path is only eligible when no target option changes output.

Comment thread Libraries/Opc.Ua.PubSub/Transcoding/NetworkMessageProfileProjector.cs Outdated
Comment thread Libraries/Opc.Ua.PubSub/Transcoding/NetworkMessageProfileProjector.cs Outdated
Comment thread Libraries/Opc.Ua.PubSub/Connections/PubSubConnection.cs
Comment thread Libraries/Opc.Ua.PubSub/Connections/PubSubConnection.cs
Comment thread Libraries/Opc.Ua.PubSub/Connections/PubSubConnection.cs Outdated
Comment thread Libraries/Opc.Ua.PubSub/Transcoding/FieldProjectionTransform.cs
- Projector: clear JsonNetworkMessageContentMask.SingleDataSetMessage when
  single-message mode is disabled; preserve source UADP DataSetMessage
  ContentMask on UADP->UADP rebuilds instead of forcing the default mask.
- Receive hook: forward the original raw frame.Payload (not the mutated,
  unwrapped framePayload) to IReceivedNetworkMessageSink; disable the
  fast-path frame for chunk-reassembled messages.
- FieldProjectionTransform: precompute the exclude HashSet in the ctor.
- Docs: merge PubSubTranscoding.md into Docs/PubSub.md (## Transcoding) and
  update the Docs/README.md link.
Comment thread Docs/PubSub.md

The DI bridge is started as a hosted service after the PubSub application is available. `PubSubTranscodingBridgeHostedService` resolves source and target connections by `Name` from `IPubSubApplication.Connections`, builds the shared `TranscodeContext`, resolves registered `INetworkMessageEncoder` instances, resolves target security through `IPubSubSecurityWrapperResolver`, and starts the bridge.

### Fluent `AddTranscodingBridge` walkthrough

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just like in the adapter package it shall be possible to set all of it up via (reloadable) configuration. When configuration changes only the areas that were reconfigured shall change.

Comment thread Docs/PubSub.md
.To("mqtt-out", TranscodeEncoding.Json)
.RemapIds(publisherId: PublisherId.FromUInt16(42))
.RenameField("Temp", "Temperature")
.SelectFields("Temperature", "Pressure")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shall be possible to promote fields into mqtt headers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants