mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-25 20:00:31 +08:00
Make the message-extension seam internal to Opc.Ua.Core
The seam is inline-only. Checking every one of its symbols against Opc.Ua.Bindings.Quic gives zero references to ISecureChannelMessageHost, ISecureChannelMessageExtension, SequenceNumberBudget, the send gate and UaSCSecureChannelRegistry alike - a QUIC data channel rides its own stream and never becomes a UASC chunk, so none of it applies. The seam therefore exists to keep the engine out of Core, not to invite other implementers: there is exactly one, and no prospect of a second. Publishing it would commit Core to an abstraction with a single implementer for ever. Core already grants InternalsVisibleTo to its sibling first-party assemblies - Opc.Ua.Client, Opc.Ua.Bindings.Quic, Opc.Ua.Bindings.Https, Opc.Ua.Core.Diagnostics - and Opc.Ua.Core.Channels had simply never been added to that list, which is the only reason the seam had to be public. Adding it lets the following become internal with no code moving at all: * ISecureChannelMessageHost and ISecureChannelMessageExtension * SequenceNumberBudget, and UaSCUaBinaryChannel.SequenceBudget * RegisterMessageExtension and TryGetMessageExtension * DataChannelExtension and TryGetDataChannelExtension, which are the inline adapter rather than API - a consumer reaches the channels through EnableDataChannels and GetDataChannels, which return the engine UaSCSecureChannelRegistry stays public deliberately. An application writing its own IServerDataChannelTransport for inline framing needs it to resolve the channel behind a request, which samples/ConsoleDataChannelStreaming demonstrates; making it internal would break a real extension point to tidy an API surface. The public surface this PR adds to Opc.Ua.Core is now UaSCSecureChannelRegistry and IUaSCSecureChannelBoundTransport, the latter being a transport extension point alongside the existing public IUaSCByteTransport. This captures the main benefit of folding the engine back into Core - not owing compatibility on a single-implementer abstraction - without moving 4866 lines of experimental engine into Core on every TFM from net472 upward, and without touching the send gate or the sequence budget, both of which are inherent to inline framing wherever its code lives. UA.slnx builds pinned to net10.0, 316 data channel tests pass, and Opc.Ua.Server.Tests is unchanged at 4072. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5d026b47-265d-4c97-8715-26e207dfa03c
This commit is contained in:
+16
-5
@@ -56,14 +56,25 @@ sequence-checks every chunk before the engine sees it. Core itself carries no da
|
||||
vocabulary — `ISecureChannelMessageExtension` and `ISecureChannelMessageHost` name only "a
|
||||
MessageType that is neither a Service call nor part of establishing the SecureChannel".
|
||||
|
||||
Both are `internal`, reached through the `InternalsVisibleTo` that Core already grants its
|
||||
sibling first-party assemblies (`Opc.Ua.Client`, `Opc.Ua.Bindings.Quic`,
|
||||
`Opc.Ua.Bindings.Https`). The seam exists to keep the engine out of Core, not to invite
|
||||
other implementers — there is exactly one, and the `opc.quic` binding does not use any of
|
||||
it, because a QUIC data channel rides its own stream and never becomes a UASC chunk.
|
||||
Publishing it would commit Core to an abstraction with one implementer for ever.
|
||||
|
||||
Two things stay in Core because they are the SecureChannel's own concerns rather than the
|
||||
feature's: `SequenceNumberBudget`, which tracks the sequence space every MessageType draws
|
||||
on, and `UaSCSecureChannelRegistry`, which maps a SecureChannel identifier to the channel
|
||||
that owns it. Because the budget is Core's, the channel claims the SequenceNumber itself
|
||||
while it secures the chunk and refuses the send with `Bad_SecureChannelTokenUnknown` when
|
||||
the space under the current token is exhausted — an extension stalls rather than reuse a
|
||||
number, and never has to reason about the serialization to do so. The `opc.quic` transport
|
||||
remains its own package, `OPCFoundation.NetStandard.Opc.Ua.Bindings.Quic`.
|
||||
that owns it. The budget is internal for the same reason as the seam; the registry is
|
||||
public because an application writing its own `IServerDataChannelTransport` for inline
|
||||
framing needs it to resolve the channel behind a request, which
|
||||
`samples/ConsoleDataChannelStreaming` demonstrates. Because the budget is Core's, the
|
||||
channel claims the SequenceNumber itself while it secures the chunk and refuses the send
|
||||
with `Bad_SecureChannelTokenUnknown` when the space under the current token is exhausted —
|
||||
an extension stalls rather than reuse a number, and never has to reason about the
|
||||
serialization to do so. The `opc.quic` transport remains its own package,
|
||||
`OPCFoundation.NetStandard.Opc.Ua.Bindings.Quic`.
|
||||
|
||||
## Server side
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Opc.Ua.Bindings
|
||||
/// This owns the STR MessageType on the channel and is the only thing that
|
||||
/// knows the frame format; the channel itself sees an opaque body.
|
||||
/// </remarks>
|
||||
public sealed class DataChannelExtension : ISecureChannelMessageExtension, IDataChannelTransport
|
||||
internal sealed class DataChannelExtension : ISecureChannelMessageExtension, IDataChannelTransport
|
||||
{
|
||||
/// <summary>
|
||||
/// Attaches data channels to a SecureChannel.
|
||||
|
||||
@@ -111,10 +111,16 @@ namespace Opc.Ua.Bindings
|
||||
/// Returns the extension that owns the STR MessageType on a
|
||||
/// SecureChannel, if one is registered.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The extension is an implementation detail of the inline framing:
|
||||
/// a consumer reaches the channels through
|
||||
/// <see cref="GetDataChannels"/>, which returns the engine rather
|
||||
/// than the adapter that drives it.
|
||||
/// </remarks>
|
||||
/// <param name="channel">The SecureChannel.</param>
|
||||
/// <param name="extension">The extension.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="channel"/> is <c>null</c>.</exception>
|
||||
public static bool TryGetDataChannelExtension(
|
||||
internal static bool TryGetDataChannelExtension(
|
||||
this UaSCUaBinaryChannel channel,
|
||||
out DataChannelExtension? extension)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<InternalsVisibleTo Include="Opc.Ua.Bindings.Https" />
|
||||
<InternalsVisibleTo Include="Opc.Ua.Bindings.Quic" />
|
||||
<InternalsVisibleTo Include="Opc.Ua.Client" />
|
||||
<InternalsVisibleTo Include="Opc.Ua.Core.Channels" />
|
||||
<InternalsVisibleTo Include="Opc.Ua.Core.Diagnostics" />
|
||||
<InternalsVisibleTo Include="Opc.Ua.Core.Diagnostics.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Opc.Ua.Bindings
|
||||
/// from the channel's single SequenceNumber space. This is the surface that
|
||||
/// makes that possible without publishing the channel's internals.
|
||||
/// </remarks>
|
||||
public interface ISecureChannelMessageHost
|
||||
internal interface ISecureChannelMessageHost
|
||||
{
|
||||
/// <summary>
|
||||
/// The largest chunk the peer accepts.
|
||||
@@ -114,7 +114,7 @@ namespace Opc.Ua.Bindings
|
||||
/// faults, which is what OPC 10000-6 §6.7.2.2 requires of a receiver that
|
||||
/// does not implement it.
|
||||
/// </remarks>
|
||||
public interface ISecureChannelMessageExtension
|
||||
internal interface ISecureChannelMessageExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// The MessageType this extension owns, without the chunk type.
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Opc.Ua.Bindings
|
||||
/// than emitting a chunk that would reuse a value.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class SequenceNumberBudget
|
||||
internal sealed class SequenceNumberBudget
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a budget.
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Opc.Ua.Bindings
|
||||
/// under the current SecurityToken. Every MessageType the channel
|
||||
/// carries draws on the same space.
|
||||
/// </summary>
|
||||
public SequenceNumberBudget SequenceBudget
|
||||
internal SequenceNumberBudget SequenceBudget
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -89,7 +89,7 @@ namespace Opc.Ua.Bindings
|
||||
/// <returns>The registered extension, which is <paramref name="extension"/>
|
||||
/// unless another was registered first.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="extension"/> is <c>null</c>.</exception>
|
||||
public ISecureChannelMessageExtension RegisterMessageExtension(
|
||||
internal ISecureChannelMessageExtension RegisterMessageExtension(
|
||||
ISecureChannelMessageExtension extension)
|
||||
{
|
||||
if (extension == null)
|
||||
@@ -105,7 +105,7 @@ namespace Opc.Ua.Bindings
|
||||
/// </summary>
|
||||
/// <param name="messageType">The MessageType, without the chunk type.</param>
|
||||
/// <param name="extension">The extension.</param>
|
||||
public bool TryGetMessageExtension(
|
||||
internal bool TryGetMessageExtension(
|
||||
uint messageType,
|
||||
out ISecureChannelMessageExtension? extension)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user