fix(opcua): scope application audit receive permissions to validation
This commit is contained in:
+433
@@ -0,0 +1,433 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Server;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Authorization;
|
||||
using ZB.MOM.WW.OtOpcUa.OpcUaServer.Security;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
|
||||
|
||||
public sealed class Sdk2ApplicationAuditPermissionsTests : IAsyncLifetime
|
||||
{
|
||||
private const string Principal = "audit-reader";
|
||||
private const string KeyId = "reader-key";
|
||||
private static CancellationToken Ct => TestContext.Current.CancellationToken;
|
||||
private readonly string _pki = Path.Combine(Path.GetTempPath(), $"otua-sdk2-audit-{Guid.NewGuid():N}");
|
||||
private OpcUaApplicationHost _host = null!;
|
||||
private OtOpcUaSdkServer _server = null!;
|
||||
private OtOpcUaNodeManager _manager = null!;
|
||||
private BaseObjectTypeState _auditType = null!;
|
||||
private BaseDataVariableState _node = null!;
|
||||
private readonly DataPlaneAuthorityState _authority = new();
|
||||
private readonly ManualTime _clock = new();
|
||||
private AuditWriteUpdateEventState Audit => _manager.BuildWriteFailureAuditEvent(
|
||||
_node, new NodeWriteOutcome(false, "device refused"), new short[] { 1, 2 }, new short[] { 7, 8 }, Principal);
|
||||
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
var started = await OpcUaHostStart.StartAsync(port => new OpcUaApplicationHost(new OpcUaApplicationHostOptions
|
||||
{
|
||||
ApplicationName = "SDK2.AuditContracts", ApplicationUri = $"urn:SDK2.AuditContracts:{Guid.NewGuid():N}",
|
||||
OpcUaPort = port, PublicHostname = "localhost", PkiStoreRoot = _pki,
|
||||
}, NullLogger<OpcUaApplicationHost>.Instance), () => new OtOpcUaSdkServer(), Ct);
|
||||
_host = started.Host;
|
||||
_server = started.Server;
|
||||
_manager = _server.NodeManager!;
|
||||
_auditType = ((DiagnosticsNodeManager)_server.CurrentInstance.DiagnosticsNodeManager)
|
||||
.FindPredefinedNode<BaseObjectTypeState>(ObjectTypeIds.AuditWriteUpdateEventType)!;
|
||||
_manager.EnsureVariable("v", null, "Value", "Int16", true, AddressSpaceRealm.Raw, isArray: true, arrayLength: 2);
|
||||
_node = _manager.TryGetVariable("v", AddressSpaceRealm.Raw)!;
|
||||
_manager.WriteValue("v", new short[] { 7, 8 }, OpcUaStatus.Good, _clock.GetUtcNow().UtcDateTime, AddressSpaceRealm.Raw);
|
||||
Apply();
|
||||
_manager.Authorizer = new DataPlaneAuthorizer(_authority, new TriePermissionEvaluator(_authority.Tries), true,
|
||||
NullLogger.Instance, _clock);
|
||||
SetAuditing(true);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _host.DisposeAsync();
|
||||
Directory.Delete(_pki, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Actual_audit_type_grants_only_the_private_application_validation_and_never_mutates_permissions()
|
||||
{
|
||||
var item = Item();
|
||||
var roles = _auditType.RolePermissions;
|
||||
var before = roles.ToArray()!.Select(x => (x.RoleId, x.Permissions)).ToArray();
|
||||
Ordinary(item).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, Audit).ShouldBe(ServiceResult.Good);
|
||||
Ordinary(item).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
_auditType.RolePermissions.ShouldBe(roles);
|
||||
_auditType.RolePermissions.ToArray()!.Select(x => (x.RoleId, x.Permissions)).ShouldBe(before);
|
||||
using var context = new OperationContext(item);
|
||||
_manager.ValidateRolePermissions(context, ObjectTypeIds.AuditWriteUpdateEventType, PermissionType.Write).StatusCode
|
||||
.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
_manager.ValidateEventReceivePermissions(context, ObjectTypeIds.AuditUpdateEventType, _node.NodeId).StatusCode
|
||||
.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("expired")]
|
||||
[InlineData("revoked")]
|
||||
[InlineData("no-read")]
|
||||
[InlineData("auditing-off")]
|
||||
[InlineData("unencrypted")]
|
||||
[InlineData("restricted")]
|
||||
[InlineData("identity-changed")]
|
||||
[InlineData("wrong-type")]
|
||||
[InlineData("unknown-source")]
|
||||
[InlineData("same-namespace-unknown-source")]
|
||||
public void Current_authority_source_type_and_transport_are_rechecked(string denial)
|
||||
{
|
||||
var item = Item();
|
||||
var audit = Audit;
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, audit).ShouldBe(ServiceResult.Good);
|
||||
switch (denial)
|
||||
{
|
||||
case "expired": _clock.Advance(TimeSpan.FromHours(2)); break;
|
||||
case "revoked": Apply(keys: false); break;
|
||||
case "no-read": Apply(NodePermissions.Browse); break;
|
||||
case "auditing-off": SetAuditing(false); break;
|
||||
case "unencrypted": item.State.Endpoint.SecurityMode = MessageSecurityMode.Sign; break;
|
||||
case "restricted":
|
||||
item.State.Identity = RoleBasedIdentity.CreateRestricted(item.State.Identity, [Role.AuthenticatedUser],
|
||||
_server.CurrentInstance.NamespaceUris);
|
||||
break;
|
||||
case "identity-changed": item.Identity = Identity(); break;
|
||||
case "wrong-type": audit.EventType!.Value = ObjectTypeIds.AuditUpdateEventType; break;
|
||||
case "unknown-source": audit.SourceNode!.Value = new NodeId("unowned", (ushort)123); break;
|
||||
case "same-namespace-unknown-source": audit.SourceNode!.Value = new NodeId("missing", _node.NodeId.NamespaceIndex); break;
|
||||
}
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, audit).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
Ordinary(item).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void User_role_permission_restrictions_are_preserved()
|
||||
{
|
||||
var item = Item();
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, Audit).ShouldBe(ServiceResult.Good);
|
||||
_auditType.UserRolePermissions = [new RolePermissionType
|
||||
{
|
||||
RoleId = ObjectIds.WellKnownRole_AuthenticatedUser, Permissions = (uint)PermissionType.Read,
|
||||
}];
|
||||
var original = _auditType.UserRolePermissions;
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, Audit).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
_auditType.UserRolePermissions.ShouldBe(original);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Authority_revoked_during_source_metadata_read_cannot_use_the_earlier_type_grant()
|
||||
{
|
||||
var item = Item();
|
||||
_node.OnReadRolePermissions = (ISystemContext _, NodeState node, ref ArrayOf<RolePermissionType> permissions) =>
|
||||
{
|
||||
Apply(keys: false);
|
||||
return ServiceResult.Good;
|
||||
};
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, Audit).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
Ordinary(item).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Existing_source_receive_restrictions_still_deny_the_scoped_validation()
|
||||
{
|
||||
var item = Item();
|
||||
_node.RolePermissions = [new RolePermissionType
|
||||
{
|
||||
RoleId = ObjectIds.WellKnownRole_AuthenticatedUser, Permissions = (uint)PermissionType.Read,
|
||||
}];
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, Audit).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(false, true, false)]
|
||||
[InlineData(true, false, false)]
|
||||
[InlineData(true, true, false)]
|
||||
[InlineData(false, false, true)]
|
||||
[InlineData(true, false, true)]
|
||||
public void Prior_callback_veto_or_fault_cleans_up_the_exact_context(bool userAttribute, bool uncertain, bool throws)
|
||||
{
|
||||
var item = Item();
|
||||
OperationContext? captured = null;
|
||||
var fault = new InvalidOperationException("controlled permission callback fault");
|
||||
NodeAttributeEventHandler<ArrayOf<RolePermissionType>> previous =
|
||||
(ISystemContext context, NodeState _, ref ArrayOf<RolePermissionType> permissions) =>
|
||||
{
|
||||
captured = ((ServerSystemContext)context).OperationContext;
|
||||
if (throws) throw fault;
|
||||
return uncertain ? StatusCodes.Uncertain : StatusCodes.BadNotSupported;
|
||||
};
|
||||
_auditType.OnReadRolePermissions = userAttribute ? null : previous;
|
||||
_auditType.OnReadUserRolePermissions = userAttribute ? previous : null;
|
||||
_manager.InstallApplicationAuditPermissions(_auditType);
|
||||
var result = _manager.ValidateApplicationAuditEventReceivePermissions(item, Audit);
|
||||
result.StatusCode.ShouldBe(throws ? StatusCodes.BadUnexpectedError
|
||||
: uncertain ? StatusCodes.Uncertain : StatusCodes.BadNotSupported);
|
||||
ServiceResult.IsGood(result).ShouldBeFalse();
|
||||
captured.ShouldNotBeNull();
|
||||
_auditType.OnReadRolePermissions = null;
|
||||
_auditType.OnReadUserRolePermissions = null;
|
||||
_manager.InstallApplicationAuditPermissions(_auditType);
|
||||
_manager.ValidateEventReceivePermissions(captured!, ObjectTypeIds.AuditWriteUpdateEventType, _node.NodeId).StatusCode
|
||||
.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
Ordinary(item).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(true, true)]
|
||||
public void Removed_or_recreated_source_cannot_use_the_original_variable_capability(bool sourceMetadata, bool recreate)
|
||||
{
|
||||
var item = Item();
|
||||
var audit = Audit;
|
||||
NodeAttributeEventHandler<ArrayOf<RolePermissionType>> previous =
|
||||
(ISystemContext _, NodeState node, ref ArrayOf<RolePermissionType> permissions) =>
|
||||
{
|
||||
_manager.RemoveVariableNode("v", AddressSpaceRealm.Raw).ShouldBeTrue();
|
||||
if (recreate)
|
||||
_manager.EnsureVariable("v", null, "New Value", "Int16", true, AddressSpaceRealm.Raw, isArray: true, arrayLength: 2);
|
||||
return ServiceResult.Good;
|
||||
};
|
||||
if (sourceMetadata) _node.OnReadRolePermissions = previous;
|
||||
else
|
||||
{
|
||||
_auditType.OnReadRolePermissions = previous;
|
||||
_manager.InstallApplicationAuditPermissions(_auditType);
|
||||
}
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, audit).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
if (recreate) _manager.TryGetVariable("v", AddressSpaceRealm.Raw).ShouldNotBeSameAs(_node);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void An_uncertain_permission_callback_never_queues_a_manual_audit(bool userAttribute)
|
||||
{
|
||||
var item = Item();
|
||||
var queued = 0;
|
||||
item.OnQueue = _ => queued++;
|
||||
NodeAttributeEventHandler<ArrayOf<RolePermissionType>> previous =
|
||||
(ISystemContext _, NodeState node, ref ArrayOf<RolePermissionType> permissions) => StatusCodes.Uncertain;
|
||||
_auditType.OnReadRolePermissions = userAttribute ? null : previous;
|
||||
_auditType.OnReadUserRolePermissions = userAttribute ? previous : null;
|
||||
_manager.InstallApplicationAuditPermissions(_auditType);
|
||||
using var context = new OperationContext(item);
|
||||
_manager.SubscribeToAllEvents(context, 1, item, false).ShouldBe(ServiceResult.Good);
|
||||
_manager.EnsureVariable("scalar", null, "Scalar", "Int32", true, AddressSpaceRealm.Raw);
|
||||
_manager.WriteValue("scalar", 2, OpcUaStatus.Good, _clock.GetUtcNow().UtcDateTime, AddressSpaceRealm.Raw);
|
||||
// This synchronous internal correction exercises the real manual dispatcher without a timing assertion.
|
||||
_manager.RevertOptimisticWriteIfNeeded("scalar", new NodeWriteOutcome(false, "refused"), 2, 1,
|
||||
StatusCodes.Good, Principal, AddressSpaceRealm.Raw);
|
||||
queued.ShouldBe(0);
|
||||
SdkValueBoundary.ToClr(_manager.TryGetVariable("scalar", AddressSpaceRealm.Raw)!.Value).ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Nested_validation_uses_independent_contexts_and_calls_the_prior_callback_first()
|
||||
{
|
||||
var item = Item();
|
||||
var depth = 0;
|
||||
var nested = 0;
|
||||
var captured = new List<OperationContext>();
|
||||
_auditType.OnReadRolePermissions = (ISystemContext context, NodeState _, ref ArrayOf<RolePermissionType> permissions) =>
|
||||
{
|
||||
var operation = ((ServerSystemContext)context).OperationContext!;
|
||||
if (operation.GetType() == typeof(OperationContext)) return ServiceResult.Good;
|
||||
captured.Add(operation);
|
||||
// The composition has not run yet: the prior callback sees only the persisted value.
|
||||
permissions.ShouldBe(_auditType.RolePermissions);
|
||||
if (depth++ == 0)
|
||||
{
|
||||
Ordinary(item).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, Audit).ShouldBe(ServiceResult.Good);
|
||||
nested++;
|
||||
}
|
||||
depth--;
|
||||
return ServiceResult.Good;
|
||||
};
|
||||
_manager.InstallApplicationAuditPermissions(_auditType);
|
||||
_manager.ValidateApplicationAuditEventReceivePermissions(item, Audit).ShouldBe(ServiceResult.Good);
|
||||
nested.ShouldBe(1);
|
||||
captured.Count.ShouldBe(2);
|
||||
captured[0].ShouldNotBeSameAs(captured[1]);
|
||||
_auditType.OnReadRolePermissions = null;
|
||||
_manager.InstallApplicationAuditPermissions(_auditType);
|
||||
foreach (var operation in captured)
|
||||
_manager.ValidateEventReceivePermissions(operation, ObjectTypeIds.AuditWriteUpdateEventType, _node.NodeId).StatusCode
|
||||
.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Parallel_ordinary_context_never_inherits_a_scoped_permission()
|
||||
{
|
||||
var item = Item();
|
||||
using var entered = new ManualResetEventSlim();
|
||||
using var release = new ManualResetEventSlim();
|
||||
_auditType.OnReadRolePermissions = (ISystemContext context, NodeState _, ref ArrayOf<RolePermissionType> permissions) =>
|
||||
{
|
||||
if (((ServerSystemContext)context).OperationContext!.GetType() != typeof(OperationContext))
|
||||
{
|
||||
entered.Set();
|
||||
release.Wait(Ct);
|
||||
}
|
||||
return ServiceResult.Good;
|
||||
};
|
||||
_manager.InstallApplicationAuditPermissions(_auditType);
|
||||
var scoped = Task.Run(() => _manager.ValidateApplicationAuditEventReceivePermissions(item, Audit), Ct);
|
||||
entered.Wait(TimeSpan.FromSeconds(5), Ct).ShouldBeTrue();
|
||||
try { Ordinary(item).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied); }
|
||||
finally { release.Set(); }
|
||||
(await scoped).ShouldBe(ServiceResult.Good);
|
||||
Ordinary(item).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Actual_failed_array_write_queues_one_application_audit_after_revoking_the_capability()
|
||||
{
|
||||
var item = Item();
|
||||
OperationContext? captured = null;
|
||||
var queued = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var count = 0;
|
||||
_auditType.OnReadRolePermissions = (ISystemContext context, NodeState _, ref ArrayOf<RolePermissionType> permissions) =>
|
||||
{
|
||||
captured ??= ((ServerSystemContext)context).OperationContext;
|
||||
return ServiceResult.Good;
|
||||
};
|
||||
_manager.InstallApplicationAuditPermissions(_auditType);
|
||||
item.OnQueue = instance =>
|
||||
{
|
||||
try
|
||||
{
|
||||
instance.ShouldBeOfType<AuditWriteUpdateEventState>().EventType!.Value.ShouldBe(ObjectTypeIds.AuditWriteUpdateEventType);
|
||||
_manager.ValidateEventReceivePermissions(captured!, ObjectTypeIds.AuditWriteUpdateEventType, _node.NodeId).StatusCode
|
||||
.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
Interlocked.Increment(ref count);
|
||||
queued.TrySetResult();
|
||||
}
|
||||
catch (Exception error) { queued.TrySetException(error); }
|
||||
};
|
||||
using var context = new OperationContext(item);
|
||||
_manager.SubscribeToAllEvents(context, 1, item, false).ShouldBe(ServiceResult.Good);
|
||||
_manager.NodeWriteGateway = new RefusingGateway();
|
||||
var data = new DataValue(SdkValueBoundary.FromClr(new short[] { 1, 2 }));
|
||||
_node.WriteAttribute(new ServerSystemContext(_server.CurrentInstance, context), Attributes.Value, default, data)
|
||||
.ShouldBe(ServiceResult.Good);
|
||||
await queued.Task.WaitAsync(TimeSpan.FromSeconds(5), Ct);
|
||||
count.ShouldBe(1);
|
||||
SdkValueBoundary.ToClr(_node.Value).ShouldBeOfType<short[]>().ShouldBe([7, 8]);
|
||||
Ordinary(item).StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
||||
}
|
||||
|
||||
private ServiceResult Ordinary(EventItem item)
|
||||
{
|
||||
using var context = new OperationContext(item);
|
||||
return _manager.ValidateEventReceivePermissions(context, ObjectTypeIds.AuditWriteUpdateEventType, _node.NodeId);
|
||||
}
|
||||
|
||||
private void SetAuditing(bool enabled)
|
||||
{
|
||||
var value = Variant.From(enabled);
|
||||
_server.CurrentInstance.ServerObject.Auditing!.OnSimpleWriteValue!(
|
||||
_manager.SystemContext, _server.CurrentInstance.ServerObject.Auditing, ref value).ShouldBe(ServiceResult.Good);
|
||||
_server.CurrentInstance.Auditing.ShouldBe(enabled);
|
||||
}
|
||||
|
||||
private RoleCarryingUserIdentity Identity() => (RoleCarryingUserIdentity)new RoleCarryingUserIdentity(
|
||||
new UserNameIdentityToken { UserName = "apikey" }, [], Principal, KeyId)
|
||||
.WithAdditionalRoles([Role.AuthenticatedUser], _server.CurrentInstance.NamespaceUris);
|
||||
|
||||
private EventItem Item()
|
||||
{
|
||||
var session = DispatchProxy.Create<ISession, SessionState>();
|
||||
var state = (SessionState)(object)session;
|
||||
state.Identity = Identity();
|
||||
return new EventItem(session, state);
|
||||
}
|
||||
|
||||
private void Apply(NodePermissions permissions = NodePermissions.Read | NodePermissions.WriteOperate, bool keys = true) =>
|
||||
_authority.Apply("audit-cluster", Guid.NewGuid().ToString(), keys ? [new ApiKey
|
||||
{
|
||||
ApiKeyId = KeyId, Name = KeyId, SecretHash = "hash", Principal = Principal, Enabled = true,
|
||||
CreatedUtc = _clock.GetUtcNow().UtcDateTime, ExpiresUtc = _clock.GetUtcNow().AddHours(1).UtcDateTime,
|
||||
}] : [], [new NodeAcl
|
||||
{
|
||||
NodeAclId = "audit-grant", ClusterId = "audit-cluster", Principal = Principal,
|
||||
ScopeKind = NodeAclScopeKind.Cluster, PermissionFlags = permissions,
|
||||
}], new Dictionary<AclScopeRef, IReadOnlyList<NodeAclPath>>());
|
||||
|
||||
private sealed class ManualTime : TimeProvider
|
||||
{
|
||||
private DateTimeOffset _utc = new(2026, 11, 10, 12, 0, 0, TimeSpan.Zero);
|
||||
public override DateTimeOffset GetUtcNow() => _utc;
|
||||
internal void Advance(TimeSpan delta) => _utc += delta;
|
||||
}
|
||||
|
||||
private sealed class RefusingGateway : IOpcUaNodeWriteGateway
|
||||
{
|
||||
public Task<NodeWriteOutcome> WriteAsync(string nodeId, object? value, AddressSpaceRealm realm, CancellationToken ct) =>
|
||||
Task.FromResult(new NodeWriteOutcome(false, "device refused"));
|
||||
}
|
||||
|
||||
public class SessionState : DispatchProxy
|
||||
{
|
||||
public IUserIdentity Identity { get; set; } = null!;
|
||||
public EndpointDescription Endpoint { get; } = new() { SecurityMode = MessageSecurityMode.SignAndEncrypt };
|
||||
protected override object? Invoke(MethodInfo? method, object?[]? args) => method?.Name switch
|
||||
{
|
||||
"get_EffectiveIdentity" or "get_Identity" => Identity,
|
||||
"get_EndpointDescription" => Endpoint,
|
||||
"get_PreferredLocales" => Array.Empty<string>(),
|
||||
"get_Id" => new NodeId("audit-test-session", 1),
|
||||
_ => throw new NotSupportedException(method?.Name),
|
||||
};
|
||||
}
|
||||
|
||||
private sealed class EventItem(ISession session, SessionState state) : IEventMonitoredItem
|
||||
{
|
||||
internal SessionState State => state;
|
||||
internal IUserIdentity? Identity { get; set; }
|
||||
internal Action<IFilterTarget>? OnQueue { get; set; }
|
||||
public IUserIdentity EffectiveIdentity => Identity ?? state.Identity;
|
||||
public ISession Session => session;
|
||||
public bool MonitoringAllEvents => true;
|
||||
public NodeId NodeId => ObjectIds.Server;
|
||||
public object ManagerHandle => null!;
|
||||
public void QueueEvent(IFilterTarget instance) => OnQueue?.Invoke(instance);
|
||||
public void QueueEvent(IFilterTarget instance, bool bypassFilter) => QueueEvent(instance);
|
||||
public void Dispose() { }
|
||||
public IAsyncNodeManager NodeManager => throw new NotSupportedException();
|
||||
public uint Id => 1;
|
||||
public uint SubscriptionId => 1;
|
||||
public bool IsDurable => false;
|
||||
public uint ClientHandle => 1;
|
||||
public ISubscription SubscriptionCallback { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
||||
public int MonitoredItemType => MonitoredItemTypeMask.Events;
|
||||
public bool IsReadyToPublish => false;
|
||||
public bool IsReadyToTrigger { get => false; set { } }
|
||||
public bool IsResendData => false;
|
||||
public MonitoringMode MonitoringMode => MonitoringMode.Reporting;
|
||||
public double SamplingInterval => 0;
|
||||
public EventFilter EventFilter => null!;
|
||||
public void SetupResendDataTrigger() { }
|
||||
public ServiceResult GetCreateResult(out MonitoredItemCreateResult result) => throw new NotSupportedException();
|
||||
public ServiceResult GetModifyResult(out MonitoredItemModifyResult result) => throw new NotSupportedException();
|
||||
public IStoredMonitoredItem ToStorableMonitoredItem() => throw new NotSupportedException();
|
||||
public MonitoringMode SetMonitoringMode(MonitoringMode monitoringMode) => throw new NotSupportedException();
|
||||
public bool Publish(OperationContext context, Queue<EventFieldList> notifications, uint maxNotificationsPerPublish) => throw new NotSupportedException();
|
||||
public ServiceResult ModifyAttributes(DiagnosticsMasks diagnosticsMasks, TimestampsToReturn timestampsToReturn,
|
||||
uint clientHandle, MonitoringFilter originalFilter, MonitoringFilter filterToUse, Opc.Ua.Range range,
|
||||
double samplingInterval, uint queueSize, bool discardOldest) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user