mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 07:07:24 +08:00
# Description Fix incomplete-message buffering, channel cleanup, session resource accounting, and enforcement of existing transport admission settings. - Share a targeted `ChunkReassemblyBudget` across server listeners. The reference default is 64 MiB, with a 32 MiB global occupancy threshold for sessionless intermediate chunks. This is priority headroom for activated sessions, not a guaranteed bootstrap partition. Single/final chunks and unrelated response/client buffers remain outside this budget. - Retain precise incoming/final payload quotas, valid Abort handling without channel teardown, terminal late-final rejection, and exactly-once buffer/reservation cleanup. - Use one assembly timer per quota/clock scope, asynchronous cleanup, a fixed deadline, a 30-second fallback for non-positive lifetimes, and best-effort ERR `BadTimeout` before closure. - Apply negotiated TCP/WebSocket/Pipe receive limits and size pipe rentals to actual chunks. - Add the optional `ISessionBindingProvider` and immutable `SessionBindingContext` seam. Managed membership counts distinct committed sessions, not responses, and handles reactivation, transfer, closure, expiry, restoration and shutdown. Read-only snapshots require token/channel/security validation and never replace service authorization. - Wire default and explicitly injected binding providers through raw TCP, Kestrel and UACP WebSockets; preserve existing callback/manager interfaces and direct construction. - Enforce existing `ConnectionRateLimiter` and channel capacity settings on Kestrel TCP and UACP WebSocket admission, with physical-transport leases surviving reverse handoff and releasing on cancellation, close, failed attachment and stop. HTTP/JSON requests are not treated as UASC channels. - Add regression coverage and documentation. Stabilize the CI cancellation test by waiting for the resolver to enter before canceling. This is the foundation only. New Balanced/FairShare/TrustedReservations policies, tenant isolation and guaranteed bootstrap reserves belong in the separate stacked follow-up, not this PR. ## Related Issues Fixes #4521. ## Validation | Scope | Result | | --- | --- | | Core transport, budget, binding-context and DI tests, net10.0 | 717 passed | | Core transport, budget, binding-context and DI tests, net48 | 664 passed, 6 platform-specific skips | | Session bindings, transfer/security/expiry/restore and hosted DI, net10.0 | 69 passed | | Same focused Server suite, net48 | 69 passed | | ServerRedundancyHandlerTests, Release/net48 | 39 passed | | ConsoleReferenceServer net10.0 | Build succeeded, zero warnings/errors | | Server and HTTPS binding netstandard2.1 | Both builds succeeded, zero warnings/errors | The new admission fixtures also passed five repeated runs after correcting asynchronous connection-context disposal. Full-solution validation, coverage comparison and CI for this update remain pending. ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [ ] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] 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. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received.
88 lines
4.1 KiB
C#
88 lines
4.1 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
#nullable enable
|
|
|
|
using System;
|
|
using NUnit.Framework;
|
|
|
|
namespace Opc.Ua.Core.Tests.Stack.Server
|
|
{
|
|
[TestFixture]
|
|
public sealed class SessionBindingContextTests
|
|
{
|
|
[TestCase(UserTokenType.Anonymous, null)]
|
|
[TestCase(UserTokenType.UserName, "user")]
|
|
[TestCase(UserTokenType.Certificate, "certificate")]
|
|
[TestCase(UserTokenType.IssuedToken, "issuer:subject")]
|
|
public void SnapshotPreservesValidatedValues(UserTokenType tokenType, string? userId)
|
|
{
|
|
var context = new SessionBindingContext(
|
|
new NodeId(1), "channel", 7, tokenType, userId,
|
|
SecurityPolicies.Basic256Sha256, MessageSecurityMode.SignAndEncrypt);
|
|
Assert.That(context.SessionId, Is.EqualTo(new NodeId(1)));
|
|
Assert.That(context.SecureChannelId, Is.EqualTo("channel"));
|
|
Assert.That(context.ActivationSequence, Is.EqualTo(7));
|
|
Assert.That(context.UserTokenType, Is.EqualTo(tokenType));
|
|
Assert.That(context.ClientUserId, Is.EqualTo(userId));
|
|
Assert.That(context.SecurityPolicyUri, Is.EqualTo(SecurityPolicies.Basic256Sha256));
|
|
Assert.That(context.SecurityMode, Is.EqualTo(MessageSecurityMode.SignAndEncrypt));
|
|
}
|
|
|
|
[TestCase("sessionId")]
|
|
[TestCase("secureChannelId")]
|
|
[TestCase("activationSequence")]
|
|
[TestCase("userTokenType")]
|
|
[TestCase("clientUserId")]
|
|
[TestCase("securityPolicyUri")]
|
|
[TestCase("securityMode")]
|
|
public void SnapshotRejectsInvalidState(string parameter)
|
|
{
|
|
Assert.That(() => new SessionBindingContext(
|
|
parameter == "sessionId" ? NodeId.Null : new NodeId(1),
|
|
parameter == "secureChannelId" ? string.Empty : "channel",
|
|
parameter == "activationSequence" ? 0 : 1,
|
|
parameter == "userTokenType" ? (UserTokenType)99 : UserTokenType.Anonymous,
|
|
parameter == "clientUserId" ? "unvalidated-user" : null,
|
|
parameter == "securityPolicyUri" ? string.Empty : SecurityPolicies.None,
|
|
parameter == "securityMode" ? MessageSecurityMode.Invalid : MessageSecurityMode.None),
|
|
Throws.InstanceOf<ArgumentException>().With.Property("ParamName").EqualTo(parameter));
|
|
}
|
|
|
|
[Test]
|
|
public void AuthenticatedSnapshotRequiresContinuityKey()
|
|
{
|
|
Assert.That(() => new SessionBindingContext(
|
|
new NodeId(1), "channel", 1, UserTokenType.UserName, null,
|
|
SecurityPolicies.None, MessageSecurityMode.None),
|
|
Throws.ArgumentException.With.Property("ParamName").EqualTo("clientUserId"));
|
|
}
|
|
}
|
|
}
|