mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-20 00:31:32 +08:00
1563 lines
65 KiB
C#
1563 lines
65 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2025 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/
|
|
* ======================================================================*/
|
|
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Opc.Ua.Client;
|
|
|
|
namespace Opc.Ua
|
|
{
|
|
/// <summary>
|
|
/// The client side interface with support for batching according to operation limits.
|
|
/// </summary>
|
|
public class SessionClientBatched : SessionClient
|
|
{
|
|
/// <summary>
|
|
/// The operation limits are used to batch the service requests.
|
|
/// </summary>
|
|
public OperationLimits OperationLimits { get; } = new();
|
|
|
|
/// <summary>
|
|
/// The server capabilities
|
|
/// </summary>
|
|
public ServerCapabilities ServerCapabilities { get; } = new();
|
|
|
|
/// <summary>
|
|
/// Intializes the object with a channel and logger factory.
|
|
/// </summary>
|
|
/// <param name="channel"></param>
|
|
/// <param name="telemetry"></param>
|
|
protected SessionClientBatched(
|
|
ITransportChannel channel,
|
|
ITelemetryContext telemetry)
|
|
: base(channel, telemetry)
|
|
{
|
|
m_telemetry = telemetry;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<BrowseResponse> BrowseAsync(
|
|
RequestHeader? requestHeader,
|
|
ViewDescription? view,
|
|
uint requestedMaxReferencesPerNode,
|
|
ArrayOf<BrowseDescription> nodesToBrowse,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerBrowse;
|
|
if (operationLimit == 0 || operationLimit >= nodesToBrowse.Count)
|
|
{
|
|
return base.BrowseAsync(
|
|
requestHeader,
|
|
view,
|
|
requestedMaxReferencesPerNode,
|
|
nodesToBrowse,
|
|
ct);
|
|
}
|
|
return BrowseBatchedAsync(
|
|
requestHeader,
|
|
view,
|
|
requestedMaxReferencesPerNode,
|
|
nodesToBrowse,
|
|
operationLimit,
|
|
ct);
|
|
|
|
async ValueTask<BrowseResponse> BrowseBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ViewDescription? view,
|
|
uint requestedMaxReferencesPerNode,
|
|
ArrayOf<BrowseDescription> nodesToBrowse,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
BrowseResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<BrowseResult>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
nodesToBrowse.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<BrowseDescription> nodesToBrowseBatch in nodesToBrowse.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.BrowseAsync(
|
|
requestHeader,
|
|
view,
|
|
requestedMaxReferencesPerNode,
|
|
nodesToBrowseBatch,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<BrowseResult> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
|
|
ValidateResponse(batchResults, nodesToBrowseBatch);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, nodesToBrowseBatch);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<BrowseNextResponse> BrowseNextAsync(
|
|
RequestHeader? requestHeader,
|
|
bool releaseContinuationPoints,
|
|
ArrayOf<ByteString> continuationPoints,
|
|
CancellationToken ct)
|
|
{
|
|
ushort operationLimit = ServerCapabilities.MaxBrowseContinuationPoints;
|
|
if (operationLimit == 0 || operationLimit >= continuationPoints.Count)
|
|
{
|
|
return base.BrowseNextAsync(
|
|
requestHeader,
|
|
releaseContinuationPoints,
|
|
continuationPoints,
|
|
ct);
|
|
}
|
|
|
|
return BrowseNextBatchedAsync(
|
|
requestHeader,
|
|
releaseContinuationPoints,
|
|
continuationPoints,
|
|
operationLimit,
|
|
ct);
|
|
|
|
async ValueTask<BrowseNextResponse> BrowseNextBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
bool releaseContinuationPoints,
|
|
ArrayOf<ByteString> continuationPoints,
|
|
ushort operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
BrowseNextResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<BrowseResult>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
continuationPoints.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<ByteString> continuationPointsBatch in continuationPoints.Batch(operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.BrowseNextAsync(
|
|
requestHeader,
|
|
releaseContinuationPoints,
|
|
continuationPointsBatch,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<BrowseResult> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
|
|
ValidateResponse(batchResults, continuationPointsBatch);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, continuationPointsBatch);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<TranslateBrowsePathsToNodeIdsResponse> TranslateBrowsePathsToNodeIdsAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<BrowsePath> browsePaths,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds;
|
|
if (operationLimit == 0 || operationLimit >= browsePaths.Count)
|
|
{
|
|
return base.TranslateBrowsePathsToNodeIdsAsync(
|
|
requestHeader,
|
|
browsePaths,
|
|
ct);
|
|
}
|
|
return TranslateBrowsePathsToNodeIdsBatchedAsync(requestHeader, browsePaths, operationLimit, ct);
|
|
|
|
async ValueTask<TranslateBrowsePathsToNodeIdsResponse> TranslateBrowsePathsToNodeIdsBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<BrowsePath> browsePaths,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
TranslateBrowsePathsToNodeIdsResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<BrowsePathResult>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
browsePaths.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<BrowsePath> batchBrowsePaths in browsePaths.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.TranslateBrowsePathsToNodeIdsAsync(
|
|
requestHeader,
|
|
batchBrowsePaths,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<BrowsePathResult> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
ValidateResponse(batchResults, batchBrowsePaths);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchBrowsePaths);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<RegisterNodesResponse> RegisterNodesAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<NodeId> nodesToRegister,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerRegisterNodes;
|
|
if (operationLimit == 0 || operationLimit >= nodesToRegister.Count)
|
|
{
|
|
return base.RegisterNodesAsync(
|
|
requestHeader,
|
|
nodesToRegister,
|
|
ct);
|
|
}
|
|
return RegisterNodesBatchedAsync(requestHeader, nodesToRegister, operationLimit, ct);
|
|
|
|
async ValueTask<RegisterNodesResponse> RegisterNodesBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<NodeId> nodesToRegister,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
RegisterNodesResponse? response = null;
|
|
var registeredNodeIds = new List<NodeId>();
|
|
foreach (ArrayOf<NodeId> batchNodesToRegister in nodesToRegister.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.RegisterNodesAsync(
|
|
requestHeader,
|
|
batchNodesToRegister,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<NodeId> batchRegisteredNodeIds = response.RegisteredNodeIds;
|
|
ValidateResponse(batchRegisteredNodeIds, batchNodesToRegister);
|
|
registeredNodeIds.AddRange(batchRegisteredNodeIds);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.RegisteredNodeIds = registeredNodeIds;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<UnregisterNodesResponse> UnregisterNodesAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<NodeId> nodesToUnregister,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerRegisterNodes;
|
|
if (operationLimit == 0 || operationLimit >= nodesToUnregister.Count)
|
|
{
|
|
return base.UnregisterNodesAsync(
|
|
requestHeader,
|
|
nodesToUnregister,
|
|
ct);
|
|
}
|
|
return UnregisterNodesBatchedAsync(requestHeader, nodesToUnregister, operationLimit, ct);
|
|
|
|
async ValueTask<UnregisterNodesResponse> UnregisterNodesBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<NodeId> nodesToUnregister,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
UnregisterNodesResponse? response = null;
|
|
foreach (ArrayOf<NodeId> batchNodesToUnregister in nodesToUnregister
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.UnregisterNodesAsync(
|
|
requestHeader,
|
|
batchNodesToUnregister,
|
|
ct).ConfigureAwait(false);
|
|
}
|
|
ValidateResponse(response);
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<ReadResponse> ReadAsync(
|
|
RequestHeader? requestHeader,
|
|
double maxAge,
|
|
TimestampsToReturn timestampsToReturn,
|
|
ArrayOf<ReadValueId> nodesToRead,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerRead;
|
|
if (operationLimit == 0 || operationLimit >= nodesToRead.Count)
|
|
{
|
|
return base.ReadAsync(
|
|
requestHeader,
|
|
maxAge,
|
|
timestampsToReturn,
|
|
nodesToRead,
|
|
ct);
|
|
}
|
|
return ReadBatchedAsync(
|
|
requestHeader,
|
|
maxAge,
|
|
timestampsToReturn,
|
|
nodesToRead,
|
|
operationLimit,
|
|
ct);
|
|
|
|
async ValueTask<ReadResponse> ReadBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
double maxAge,
|
|
TimestampsToReturn timestampsToReturn,
|
|
ArrayOf<ReadValueId> nodesToRead,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
ReadResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<DataValue>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
nodesToRead.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<ReadValueId> batchAttributesToRead in nodesToRead
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.ReadAsync(
|
|
requestHeader,
|
|
maxAge,
|
|
timestampsToReturn,
|
|
batchAttributesToRead,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<DataValue> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
|
|
ValidateResponse(batchResults, batchAttributesToRead);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchAttributesToRead);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<HistoryReadResponse> HistoryReadAsync(
|
|
RequestHeader? requestHeader,
|
|
ExtensionObject historyReadDetails,
|
|
TimestampsToReturn timestampsToReturn,
|
|
bool releaseContinuationPoints,
|
|
ArrayOf<HistoryReadValueId> nodesToRead,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerHistoryReadData;
|
|
if (historyReadDetails.TypeId == DataTypeIds.ReadEventDetails ||
|
|
historyReadDetails.TryGetValue(out ReadEventDetails? _))
|
|
{
|
|
operationLimit = OperationLimits.MaxNodesPerHistoryReadEvents;
|
|
}
|
|
|
|
if (operationLimit == 0 || operationLimit >= nodesToRead.Count)
|
|
{
|
|
return base.HistoryReadAsync(
|
|
requestHeader,
|
|
historyReadDetails,
|
|
timestampsToReturn,
|
|
releaseContinuationPoints,
|
|
nodesToRead,
|
|
ct);
|
|
}
|
|
return HistoryReadBatchedAsync(
|
|
requestHeader,
|
|
historyReadDetails,
|
|
timestampsToReturn,
|
|
releaseContinuationPoints,
|
|
nodesToRead,
|
|
operationLimit,
|
|
ct);
|
|
|
|
async ValueTask<HistoryReadResponse> HistoryReadBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ExtensionObject historyReadDetails,
|
|
TimestampsToReturn timestampsToReturn,
|
|
bool releaseContinuationPoints,
|
|
ArrayOf<HistoryReadValueId> nodesToRead,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
HistoryReadResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<HistoryReadResult>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
nodesToRead.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<HistoryReadValueId> batchNodesToRead in nodesToRead
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.HistoryReadAsync(
|
|
requestHeader,
|
|
historyReadDetails,
|
|
timestampsToReturn,
|
|
releaseContinuationPoints,
|
|
batchNodesToRead,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<HistoryReadResult> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
|
|
ValidateResponse(batchResults, batchNodesToRead);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchNodesToRead);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<WriteResponse> WriteAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<WriteValue> nodesToWrite,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerWrite;
|
|
if (operationLimit == 0 || operationLimit >= nodesToWrite.Count)
|
|
{
|
|
return base.WriteAsync(
|
|
requestHeader,
|
|
nodesToWrite,
|
|
ct);
|
|
}
|
|
return WriteBatchedAsync(requestHeader, nodesToWrite, operationLimit, ct);
|
|
|
|
async ValueTask<WriteResponse> WriteBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<WriteValue> nodesToWrite,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
WriteResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<StatusCode>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
nodesToWrite.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<WriteValue> batchNodesToWrite in nodesToWrite
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.WriteAsync(
|
|
requestHeader,
|
|
batchNodesToWrite,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<StatusCode> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
|
|
ValidateResponse(batchResults, batchNodesToWrite);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchNodesToWrite);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<HistoryUpdateResponse> HistoryUpdateAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<ExtensionObject> historyUpdateDetails,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerHistoryUpdateData;
|
|
if (historyUpdateDetails.Count > 0 &&
|
|
(historyUpdateDetails[0].TypeId == DataTypeIds.UpdateEventDetails ||
|
|
historyUpdateDetails[0].TryGetValue(out UpdateEventDetails? _)))
|
|
{
|
|
operationLimit = OperationLimits.MaxNodesPerHistoryUpdateEvents;
|
|
}
|
|
if (operationLimit == 0 || operationLimit >= historyUpdateDetails.Count)
|
|
{
|
|
return base.HistoryUpdateAsync(
|
|
requestHeader,
|
|
historyUpdateDetails,
|
|
ct);
|
|
}
|
|
return HistoryUpdateBatchedAsync(requestHeader, historyUpdateDetails, operationLimit, ct);
|
|
|
|
async ValueTask<HistoryUpdateResponse> HistoryUpdateBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<ExtensionObject> historyUpdateDetails,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
HistoryUpdateResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<HistoryUpdateResult>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
historyUpdateDetails.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<ExtensionObject> batchHistoryUpdateDetails in historyUpdateDetails
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.HistoryUpdateAsync(
|
|
requestHeader,
|
|
batchHistoryUpdateDetails,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<HistoryUpdateResult> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
|
|
ValidateResponse(batchResults, batchHistoryUpdateDetails);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchHistoryUpdateDetails);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<CallResponse> CallAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<CallMethodRequest> methodsToCall,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerMethodCall;
|
|
if (operationLimit == 0 || operationLimit >= methodsToCall.Count)
|
|
{
|
|
return base.CallAsync(
|
|
requestHeader,
|
|
methodsToCall,
|
|
ct);
|
|
}
|
|
return CallBatchedAsync(requestHeader, methodsToCall, operationLimit, ct);
|
|
|
|
async ValueTask<CallResponse> CallBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<CallMethodRequest> methodsToCall,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
CallResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<CallMethodResult>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
methodsToCall.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<CallMethodRequest> batchMethodsToCall in methodsToCall
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.CallAsync(
|
|
requestHeader,
|
|
batchMethodsToCall,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<CallMethodResult> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
|
|
ValidateResponse(batchResults, batchMethodsToCall);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchMethodsToCall);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<CreateMonitoredItemsResponse> CreateMonitoredItemsAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
TimestampsToReturn timestampsToReturn,
|
|
ArrayOf<MonitoredItemCreateRequest> itemsToCreate,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxMonitoredItemsPerCall;
|
|
if (operationLimit == 0 || operationLimit >= itemsToCreate.Count)
|
|
{
|
|
return base.CreateMonitoredItemsAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
timestampsToReturn,
|
|
itemsToCreate,
|
|
ct);
|
|
}
|
|
|
|
return CreateMonitoredItemsBatchedAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
timestampsToReturn,
|
|
itemsToCreate,
|
|
operationLimit,
|
|
ct);
|
|
|
|
async ValueTask<CreateMonitoredItemsResponse> CreateMonitoredItemsBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
TimestampsToReturn timestampsToReturn,
|
|
ArrayOf<MonitoredItemCreateRequest> itemsToCreate,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
CreateMonitoredItemsResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<MonitoredItemCreateResult>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
itemsToCreate.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<MonitoredItemCreateRequest> batchItemsToCreate in itemsToCreate
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.CreateMonitoredItemsAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
timestampsToReturn,
|
|
batchItemsToCreate,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<MonitoredItemCreateResult> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
ValidateResponse(batchResults, batchItemsToCreate);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchItemsToCreate);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<ModifyMonitoredItemsResponse> ModifyMonitoredItemsAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
TimestampsToReturn timestampsToReturn,
|
|
ArrayOf<MonitoredItemModifyRequest> itemsToModify,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxMonitoredItemsPerCall;
|
|
if (operationLimit == 0 || operationLimit >= itemsToModify.Count)
|
|
{
|
|
return base.ModifyMonitoredItemsAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
timestampsToReturn,
|
|
itemsToModify,
|
|
ct);
|
|
}
|
|
return ModifyMonitoredItemsBatchedAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
timestampsToReturn,
|
|
itemsToModify,
|
|
operationLimit,
|
|
ct);
|
|
|
|
async ValueTask<ModifyMonitoredItemsResponse> ModifyMonitoredItemsBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
TimestampsToReturn timestampsToReturn,
|
|
ArrayOf<MonitoredItemModifyRequest> itemsToModify,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
ModifyMonitoredItemsResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<MonitoredItemModifyResult>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
itemsToModify.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<MonitoredItemModifyRequest> batchItemsToModify in itemsToModify
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.ModifyMonitoredItemsAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
timestampsToReturn,
|
|
batchItemsToModify,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<MonitoredItemModifyResult> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
ValidateResponse(batchResults, batchItemsToModify);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchItemsToModify);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<SetMonitoringModeResponse> SetMonitoringModeAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
MonitoringMode monitoringMode,
|
|
ArrayOf<uint> monitoredItemIds,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxMonitoredItemsPerCall;
|
|
if (operationLimit == 0 || operationLimit >= monitoredItemIds.Count)
|
|
{
|
|
return base.SetMonitoringModeAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
monitoringMode,
|
|
monitoredItemIds,
|
|
ct);
|
|
}
|
|
|
|
return SetMonitoringModeBatchedAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
monitoringMode,
|
|
monitoredItemIds,
|
|
operationLimit,
|
|
ct);
|
|
|
|
async ValueTask<SetMonitoringModeResponse> SetMonitoringModeBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
MonitoringMode monitoringMode,
|
|
ArrayOf<uint> monitoredItemIds,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
SetMonitoringModeResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<StatusCode>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
monitoredItemIds.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<uint> batchMonitoredItemIds in monitoredItemIds
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.SetMonitoringModeAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
monitoringMode,
|
|
batchMonitoredItemIds,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<StatusCode> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
|
|
ValidateResponse(batchResults, batchMonitoredItemIds);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchMonitoredItemIds);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<SetTriggeringResponse> SetTriggeringAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
uint triggeringItemId,
|
|
ArrayOf<uint> linksToAdd,
|
|
ArrayOf<uint> linksToRemove,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxMonitoredItemsPerCall;
|
|
if (operationLimit == 0 ||
|
|
operationLimit >= linksToAdd.Count + linksToRemove.Count)
|
|
{
|
|
return base.SetTriggeringAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
triggeringItemId,
|
|
linksToAdd,
|
|
linksToRemove,
|
|
ct);
|
|
}
|
|
return SetTriggeringBatchedAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
triggeringItemId,
|
|
linksToAdd,
|
|
linksToRemove,
|
|
operationLimit,
|
|
ct);
|
|
|
|
async ValueTask<SetTriggeringResponse> SetTriggeringBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
uint triggeringItemId,
|
|
ArrayOf<uint> linksToAdd,
|
|
ArrayOf<uint> linksToRemove,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
SetTriggeringResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<StatusCode>? addResults,
|
|
out List<DiagnosticInfo>? addDiagnosticInfos,
|
|
out List<string> stringTable,
|
|
linksToAdd.Count,
|
|
operationLimit);
|
|
InitResponseCollections(
|
|
out List<StatusCode>? removeResults,
|
|
out List<DiagnosticInfo>? removeDiagnosticInfos,
|
|
out _,
|
|
linksToRemove.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<uint> batchLinksToAdd in linksToAdd
|
|
.Batch((int)operationLimit))
|
|
{
|
|
ArrayOf<uint> batchLinksToRemove;
|
|
if (operationLimit == 0)
|
|
{
|
|
batchLinksToRemove = linksToRemove;
|
|
linksToRemove = [];
|
|
}
|
|
else if (batchLinksToAdd.Count < operationLimit)
|
|
{
|
|
batchLinksToRemove = linksToRemove[..((int)operationLimit - batchLinksToAdd.Count)];
|
|
linksToRemove = linksToRemove[batchLinksToRemove.Count..];
|
|
}
|
|
else
|
|
{
|
|
batchLinksToRemove = [];
|
|
}
|
|
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.SetTriggeringAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
triggeringItemId,
|
|
batchLinksToAdd,
|
|
batchLinksToRemove,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<StatusCode> batchAddResults = response.AddResults;
|
|
ArrayOf<DiagnosticInfo> batchAddDiagnosticInfos = response.AddDiagnosticInfos;
|
|
ArrayOf<StatusCode> batchRemoveResults = response.RemoveResults;
|
|
ArrayOf<DiagnosticInfo> batchRemoveDiagnosticInfos = response.RemoveDiagnosticInfos;
|
|
|
|
ValidateResponse(batchAddResults, batchLinksToAdd);
|
|
ValidateDiagnosticInfos(batchAddDiagnosticInfos, batchLinksToAdd);
|
|
ValidateResponse(batchRemoveResults, batchLinksToRemove);
|
|
ValidateDiagnosticInfos(batchRemoveDiagnosticInfos, batchLinksToRemove);
|
|
|
|
AddResponses(
|
|
ref addResults,
|
|
ref addDiagnosticInfos,
|
|
ref stringTable,
|
|
batchAddResults,
|
|
batchAddDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
|
|
AddResponses(
|
|
ref removeResults,
|
|
ref removeDiagnosticInfos,
|
|
ref stringTable,
|
|
batchRemoveResults,
|
|
batchRemoveDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
if (linksToRemove.Count > 0)
|
|
{
|
|
foreach (ArrayOf<uint> batchLinksToRemove in linksToRemove
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
var batchLinksToAdd = ArrayOf.Empty<uint>();
|
|
response = await base.SetTriggeringAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
triggeringItemId,
|
|
batchLinksToAdd,
|
|
batchLinksToRemove,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<StatusCode> batchAddResults = response.AddResults;
|
|
ArrayOf<DiagnosticInfo> batchAddDiagnosticInfos = response.AddDiagnosticInfos;
|
|
ArrayOf<StatusCode> batchRemoveResults = response.RemoveResults;
|
|
ArrayOf<DiagnosticInfo> batchRemoveDiagnosticInfos = response.RemoveDiagnosticInfos;
|
|
|
|
ValidateResponse(batchAddResults, batchLinksToAdd);
|
|
ValidateDiagnosticInfos(batchAddDiagnosticInfos, batchLinksToAdd);
|
|
ValidateResponse(batchRemoveResults, batchLinksToRemove);
|
|
ValidateDiagnosticInfos(batchRemoveDiagnosticInfos, batchLinksToRemove);
|
|
|
|
AddResponses(
|
|
ref addResults,
|
|
ref addDiagnosticInfos,
|
|
ref stringTable,
|
|
batchAddResults,
|
|
batchAddDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
|
|
AddResponses(
|
|
ref removeResults,
|
|
ref removeDiagnosticInfos,
|
|
ref stringTable,
|
|
batchRemoveResults,
|
|
batchRemoveDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.AddResults = addResults;
|
|
response.AddDiagnosticInfos = addDiagnosticInfos;
|
|
response.RemoveResults = removeResults;
|
|
response.RemoveDiagnosticInfos = removeDiagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<DeleteMonitoredItemsResponse> DeleteMonitoredItemsAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
ArrayOf<uint> monitoredItemIds,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxMonitoredItemsPerCall;
|
|
if (operationLimit == 0 || operationLimit >= monitoredItemIds.Count)
|
|
{
|
|
return base.DeleteMonitoredItemsAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
monitoredItemIds,
|
|
ct);
|
|
}
|
|
return DeleteMonitoredItemsBatchedAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
monitoredItemIds,
|
|
operationLimit,
|
|
ct);
|
|
|
|
async ValueTask<DeleteMonitoredItemsResponse> DeleteMonitoredItemsBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
uint subscriptionId,
|
|
ArrayOf<uint> monitoredItemIds,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
DeleteMonitoredItemsResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<StatusCode>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
monitoredItemIds.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<uint> batchMonitoredItemIds in monitoredItemIds
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.DeleteMonitoredItemsAsync(
|
|
requestHeader,
|
|
subscriptionId,
|
|
batchMonitoredItemIds,
|
|
ct).ConfigureAwait(false);
|
|
ArrayOf<StatusCode> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
|
|
ValidateResponse(batchResults, batchMonitoredItemIds);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchMonitoredItemIds);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<AddNodesResponse> AddNodesAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<AddNodesItem> nodesToAdd,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerNodeManagement;
|
|
if (operationLimit == 0 || operationLimit >= nodesToAdd.Count)
|
|
{
|
|
return base.AddNodesAsync(
|
|
requestHeader,
|
|
nodesToAdd,
|
|
ct);
|
|
}
|
|
|
|
return AddNodesBatchedAsync(requestHeader, nodesToAdd, operationLimit, ct);
|
|
|
|
async ValueTask<AddNodesResponse> AddNodesBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<AddNodesItem> nodesToAdd,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
AddNodesResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<AddNodesResult>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
nodesToAdd.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<AddNodesItem> batchNodesToAdd in nodesToAdd
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.AddNodesAsync(requestHeader, batchNodesToAdd,
|
|
ct).ConfigureAwait(false);
|
|
ArrayOf<AddNodesResult> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
ValidateResponse(batchResults, batchNodesToAdd);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchNodesToAdd);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<AddReferencesResponse> AddReferencesAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<AddReferencesItem> referencesToAdd,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerNodeManagement;
|
|
if (operationLimit == 0 || operationLimit >= referencesToAdd.Count)
|
|
{
|
|
return base.AddReferencesAsync(
|
|
requestHeader,
|
|
referencesToAdd,
|
|
ct);
|
|
}
|
|
|
|
return AddReferencesBatchedAsync(requestHeader, referencesToAdd, operationLimit, ct);
|
|
|
|
async ValueTask<AddReferencesResponse> AddReferencesBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<AddReferencesItem> referencesToAdd,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
AddReferencesResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<StatusCode>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
referencesToAdd.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<AddReferencesItem> batchReferencesToAdd in referencesToAdd
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.AddReferencesAsync(
|
|
requestHeader,
|
|
batchReferencesToAdd,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<StatusCode> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
ValidateResponse(batchResults, batchReferencesToAdd);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchReferencesToAdd);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<DeleteNodesResponse> DeleteNodesAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<DeleteNodesItem> nodesToDelete,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerNodeManagement;
|
|
if (operationLimit == 0 || operationLimit >= nodesToDelete.Count)
|
|
{
|
|
return base.DeleteNodesAsync(
|
|
requestHeader,
|
|
nodesToDelete,
|
|
ct);
|
|
}
|
|
|
|
return DeleteNodesBatchedAsync(requestHeader, nodesToDelete, operationLimit, ct);
|
|
|
|
async ValueTask<DeleteNodesResponse> DeleteNodesBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<DeleteNodesItem> nodesToDelete,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
DeleteNodesResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<StatusCode>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
nodesToDelete.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<DeleteNodesItem> batchNodesToDelete in nodesToDelete
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.DeleteNodesAsync(
|
|
requestHeader,
|
|
batchNodesToDelete,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<StatusCode> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
ValidateResponse(batchResults, batchNodesToDelete);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchNodesToDelete);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override ValueTask<DeleteReferencesResponse> DeleteReferencesAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<DeleteReferencesItem> referencesToDelete,
|
|
CancellationToken ct)
|
|
{
|
|
uint operationLimit = OperationLimits.MaxNodesPerNodeManagement;
|
|
if (operationLimit == 0 || operationLimit >= referencesToDelete.Count)
|
|
{
|
|
return base.DeleteReferencesAsync(
|
|
requestHeader,
|
|
referencesToDelete,
|
|
ct);
|
|
}
|
|
|
|
return DeleteReferencesBatchedAsync(requestHeader, referencesToDelete, operationLimit, ct);
|
|
|
|
async ValueTask<DeleteReferencesResponse> DeleteReferencesBatchedAsync(
|
|
RequestHeader? requestHeader,
|
|
ArrayOf<DeleteReferencesItem> referencesToDelete,
|
|
uint operationLimit,
|
|
CancellationToken ct)
|
|
{
|
|
using Activity? activity = m_telemetry.StartActivity();
|
|
requestHeader ??= new RequestHeader();
|
|
DeleteReferencesResponse? response = null;
|
|
InitResponseCollections(
|
|
out List<StatusCode>? results,
|
|
out List<DiagnosticInfo>? diagnosticInfos,
|
|
out List<string> stringTable,
|
|
referencesToDelete.Count,
|
|
operationLimit);
|
|
foreach (ArrayOf<DeleteReferencesItem> batchReferencesToDelete in referencesToDelete
|
|
.Batch((int)operationLimit))
|
|
{
|
|
requestHeader.RequestHandle = 0;
|
|
response = await base.DeleteReferencesAsync(
|
|
requestHeader,
|
|
batchReferencesToDelete,
|
|
ct).ConfigureAwait(false);
|
|
|
|
ArrayOf<StatusCode> batchResults = response.Results;
|
|
ArrayOf<DiagnosticInfo> batchDiagnosticInfos = response.DiagnosticInfos;
|
|
ValidateResponse(batchResults, batchReferencesToDelete);
|
|
ValidateDiagnosticInfos(batchDiagnosticInfos, batchReferencesToDelete);
|
|
|
|
AddResponses(
|
|
ref results,
|
|
ref diagnosticInfos,
|
|
ref stringTable,
|
|
batchResults,
|
|
batchDiagnosticInfos,
|
|
response.ResponseHeader.StringTable);
|
|
}
|
|
|
|
ValidateResponse(response);
|
|
response.Results = results;
|
|
response.DiagnosticInfos = diagnosticInfos;
|
|
response.ResponseHeader.StringTable = stringTable;
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check response for null
|
|
/// </summary>
|
|
/// <param name="response"></param>
|
|
/// <exception cref="ServiceResultException"></exception>
|
|
private static void ValidateResponse([NotNull] IServiceResponse? response)
|
|
{
|
|
if (response is null)
|
|
{
|
|
throw ServiceResultException.Unexpected(
|
|
"Empty ops should have been covered in fast path");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize the collections for a service call.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="results"></param>
|
|
/// <param name="diagnosticInfos"></param>
|
|
/// <param name="stringTable"></param>
|
|
/// <param name="count"></param>
|
|
/// <param name="operationLimit"></param>
|
|
/// <remarks>
|
|
/// Preset the result collections with null if the operation limit
|
|
/// is sufficient or with the final size if batching is necessary.
|
|
/// </remarks>
|
|
private static void InitResponseCollections<T>(
|
|
out List<T> results,
|
|
out List<DiagnosticInfo> diagnosticInfos,
|
|
out List<string> stringTable,
|
|
int count,
|
|
uint operationLimit)
|
|
{
|
|
Debug.Assert(count > operationLimit);
|
|
results = new List<T>(count);
|
|
diagnosticInfos = new List<DiagnosticInfo>(count);
|
|
stringTable = [];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add the result of a batched service call to the results.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="results"></param>
|
|
/// <param name="diagnosticInfos"></param>
|
|
/// <param name="stringTable"></param>
|
|
/// <param name="batchedResults"></param>
|
|
/// <param name="batchedDiagnosticInfos"></param>
|
|
/// <param name="batchedStringTable"></param>
|
|
/// <remarks>
|
|
/// Assigns the batched collection result to the result if the result
|
|
/// collection is not initialized, adds the range to the result
|
|
/// collections otherwise.
|
|
/// The string table indexes are updated in the diagnostic infos if necessary.
|
|
/// </remarks>
|
|
private static void AddResponses<T>(
|
|
ref List<T> results,
|
|
ref List<DiagnosticInfo> diagnosticInfos,
|
|
ref List<string> stringTable,
|
|
ArrayOf<T> batchedResults,
|
|
ArrayOf<DiagnosticInfo> batchedDiagnosticInfos,
|
|
ArrayOf<string> batchedStringTable)
|
|
{
|
|
bool hasDiagnosticInfos = diagnosticInfos.Count > 0;
|
|
bool hasEmptyDiagnosticInfos = diagnosticInfos.Count == 0 && results.Count > 0;
|
|
bool hasBatchDiagnosticInfos = batchedDiagnosticInfos.Count > 0;
|
|
int correctionCount = 0;
|
|
if (hasBatchDiagnosticInfos && hasEmptyDiagnosticInfos)
|
|
{
|
|
correctionCount = results.Count;
|
|
}
|
|
else if (!hasBatchDiagnosticInfos && hasDiagnosticInfos)
|
|
{
|
|
correctionCount = batchedResults.Count;
|
|
}
|
|
if (correctionCount > 0)
|
|
{
|
|
// fill missing diagnostics infos with null entries
|
|
for (int i = 0; i < correctionCount; i++)
|
|
{
|
|
// DiagnosticInfoCollection accepts null sentinels even though the
|
|
// declared element type is non-nullable; null marks a "missing" slot.
|
|
diagnosticInfos.Add(null!);
|
|
}
|
|
}
|
|
else if (batchedStringTable.Count > 0)
|
|
{
|
|
// correct indexes in the string table
|
|
int stringTableOffset = stringTable.Count;
|
|
foreach (DiagnosticInfo? diagnosticInfo in batchedDiagnosticInfos)
|
|
{
|
|
UpdateDiagnosticInfoIndexes(diagnosticInfo, stringTableOffset);
|
|
}
|
|
}
|
|
|
|
results.AddRange(batchedResults);
|
|
diagnosticInfos.AddRange(batchedDiagnosticInfos);
|
|
stringTable.AddRange(batchedStringTable);
|
|
|
|
static void UpdateDiagnosticInfoIndexes(DiagnosticInfo? diagnosticInfo,
|
|
int stringTableOffset)
|
|
{
|
|
int depth = 0;
|
|
while (diagnosticInfo != null && depth++ < DiagnosticInfo.MaxInnerDepth)
|
|
{
|
|
if (diagnosticInfo.LocalizedText >= 0)
|
|
{
|
|
diagnosticInfo.LocalizedText += stringTableOffset;
|
|
}
|
|
if (diagnosticInfo.Locale >= 0)
|
|
{
|
|
diagnosticInfo.Locale += stringTableOffset;
|
|
}
|
|
if (diagnosticInfo.NamespaceUri >= 0)
|
|
{
|
|
diagnosticInfo.NamespaceUri += stringTableOffset;
|
|
}
|
|
if (diagnosticInfo.SymbolicId >= 0)
|
|
{
|
|
diagnosticInfo.SymbolicId += stringTableOffset;
|
|
}
|
|
diagnosticInfo = diagnosticInfo.InnerDiagnosticInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly ITelemetryContext m_telemetry;
|
|
}
|
|
}
|