mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-25 20:00:31 +08:00
* Add OPC UA Part 17 (Alias Names) support Server-side: pluggable IAliasNameStore + thread-safe InMemoryAliasNameStore, AliasNameStoreRegistry (live dispatcher, surfaces through optional IAliasNameStoreRegistryProvider on ServerInternalData), AliasNameNodeManager (standalone CustomNodeManager2; defaults to SecurityAdmin-only mutations on SignAndEncrypt channels), AliasNameMethodDispatcher bridging generated typed FindAlias/FindAliasVerbose/Add/Delete *MethodState.OnCallAsync handlers, AliasNameWildcardMatcher (Part 4 §7.40 Like operator with proper escape handling), and a DiagnosticsNodeManager partial that late-binds the standard well-known Aliases (i=23470), TagVariables (i=23479) and Topics (i=23488) FindAlias methods plus Aliases.LastChange through the registry. Client-side: AliasNameClient (OpenStandardAliases/TagVariables/Topics + arbitrary category root) covering all four Part 17 methods, sub-category enumeration and ReadLastChangeAsync. Hardcoded method-id fast-path for standard categories; cached TranslateBrowsePaths resolution for custom ones. Service-level errors map to typed exceptions (UnauthorizedAccessException / NotSupportedException). AliasNameResolver (IAsyncDisposable) gives a name->NodeId cache with forward and reverse lookup; default refresh is Manual with opt-in AutoOnLastChange polling that compares VersionTime on inequality (wrap-safe). Reference server: registers an InMemoryAliasNameStore for the standard TagVariables + Topics categories with sample seeds (TIC101_Setpoint, TIC101_PV, FIC202_Flow, Pump1_Status, Heater_Power, MultiRefAlias, ServerEvents, AuditEvents) so the standard well-known FindAlias methods return data through the DiagnosticsNodeManager late binder. Tests: 45 server-side cases (wildcard matcher, in-memory store, registry, node manager) plus 11 mocked-session client cases and 4 end-to-end integration cases against a live ReferenceServer over a real OPC UA channel. All 60 tests pass; full solution builds clean across net472/net48/netstandard2.1/net8.0/net9.0/net10.0. Docs: Docs/Part17AliasNames.md plus a link from Docs/README.md.
175 lines
8.4 KiB
C#
175 lines
8.4 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;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Opc.Ua.Server.AliasNames
|
|
{
|
|
/// <summary>
|
|
/// Pluggable backend for an OPC UA Part 17 alias-name provider. One
|
|
/// store may serve multiple categories — each declared via
|
|
/// <see cref="RootCategories"/> together with their sub-categories — and
|
|
/// is the authoritative source for both queries
|
|
/// (<c>FindAlias</c>/<c>FindAliasVerbose</c>) and mutations
|
|
/// (<c>AddAliasesToCategory</c>/<c>DeleteAliasesFromCategory</c>) on
|
|
/// those categories.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// All operations are async and accept a <see cref="CancellationToken"/>.
|
|
/// Implementations MUST be thread-safe.
|
|
/// </para>
|
|
/// <para>
|
|
/// <c>categoryId</c> arguments identify any category exposed by this
|
|
/// store — root or nested. When an operation is invoked on a parent
|
|
/// category, <c>FindAlias</c>/<c>FindAliasVerbose</c> implementations
|
|
/// MUST also include results from descendant categories per Part 17
|
|
/// §6.3.2; <c>AddAliasesAsync</c>/<c>DeleteAliasesAsync</c> however
|
|
/// apply only to the named category itself, never its descendants.
|
|
/// </para>
|
|
/// </remarks>
|
|
public interface IAliasNameStore
|
|
{
|
|
/// <summary>
|
|
/// The (immutable) root categories this store owns. Used by
|
|
/// <c>AliasNameNodeManager</c> to build the address space and
|
|
/// by <c>IAliasNameStoreRegistry</c> to route well-known
|
|
/// standard-node dispatches. Standard well-known categories should
|
|
/// use <c>ObjectIds.Aliases</c>, <c>ObjectIds.TagVariables</c> and
|
|
/// <c>ObjectIds.Topics</c> as their identifiers.
|
|
/// </summary>
|
|
IReadOnlyList<AliasNameCategoryDescriptor> RootCategories { get; }
|
|
|
|
/// <summary>
|
|
/// Raised after a successful <see cref="AddAliasesAsync"/> or
|
|
/// <see cref="DeleteAliasesAsync"/> batch — carries the new
|
|
/// <c>LastChange</c> value (VersionTime) for the affected
|
|
/// category.
|
|
/// </summary>
|
|
event EventHandler<AliasStoreChangedEventArgs>? Changed;
|
|
|
|
/// <summary>
|
|
/// Returns the current <c>LastChange</c> value (Part 17 §6.3.1) for
|
|
/// the named category. Returns <c>null</c> when the category is
|
|
/// unknown to this store or does not expose the
|
|
/// <see cref="AliasNameCapabilities.LastChange"/> capability.
|
|
/// </summary>
|
|
/// <param name="categoryId">The category's <see cref="NodeId"/>.</param>
|
|
uint? GetLastChange(NodeId categoryId);
|
|
|
|
/// <summary>
|
|
/// Returns <c>true</c> if <paramref name="categoryId"/> identifies
|
|
/// any category (root or nested) owned by this store.
|
|
/// </summary>
|
|
bool OwnsCategory(NodeId categoryId);
|
|
|
|
/// <summary>
|
|
/// Implements <c>FindAlias</c> (Part 17 §6.3.2): returns all aliases
|
|
/// under <paramref name="categoryId"/> (recursively, including
|
|
/// sub-categories) whose name matches the wildcard
|
|
/// <paramref name="aliasNameSearchPattern"/> and whose
|
|
/// <c>ReferenceTypeId</c> is, or is a subtype of,
|
|
/// <paramref name="referenceTypeFilter"/>.
|
|
/// </summary>
|
|
/// <param name="categoryId">The category to search.</param>
|
|
/// <param name="aliasNameSearchPattern">An OPC UA Like-operator
|
|
/// pattern (Part 4 §7.40). An empty pattern returns no
|
|
/// matches.</param>
|
|
/// <param name="referenceTypeFilter">Reference type to filter by;
|
|
/// <c>NodeId.Null</c> or <c>ReferenceTypeIds.References</c> matches
|
|
/// any reference type.</param>
|
|
/// <param name="typeTree">The server type tree used to evaluate
|
|
/// subtype membership.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>The matching aliases as Part 17 §7.2
|
|
/// <c>AliasNameDataType</c> records. Returns an empty list when the
|
|
/// category is unknown or there are no matches.</returns>
|
|
ValueTask<IReadOnlyList<AliasNameDataType>> FindAliasAsync(
|
|
NodeId categoryId,
|
|
string? aliasNameSearchPattern,
|
|
NodeId referenceTypeFilter,
|
|
ITypeTable typeTree,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Implements <c>FindAliasVerbose</c> (Part 17 §6.3.3) — same
|
|
/// filtering as <see cref="FindAliasAsync"/> but returns
|
|
/// <c>AliasNameVerboseDataType</c> records (Part 17 §7.3) including
|
|
/// per-target <c>ServerUris</c> and the <c>AliasNameCategoryId</c>
|
|
/// of the alias's home category.
|
|
/// </summary>
|
|
ValueTask<IReadOnlyList<AliasNameVerboseDataType>> FindAliasVerboseAsync(
|
|
NodeId categoryId,
|
|
string? aliasNameSearchPattern,
|
|
NodeId referenceTypeFilter,
|
|
ITypeTable typeTree,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Implements <c>AddAliasesToCategory</c> (Part 17 §6.3.4) — adds
|
|
/// each request as a single alias-to-target mapping in the named
|
|
/// category. Returns one <see cref="StatusCode"/> per request entry
|
|
/// in input order; typical per-entry failures are
|
|
/// <c>BadBrowseNameDuplicated</c> when the mapping already exists,
|
|
/// <c>BadNodeIdInvalid</c>/<c>BadReferenceTypeIdInvalid</c> for
|
|
/// malformed entries, or <c>BadOutOfRange</c> when the store is at
|
|
/// capacity. <c>LastChange</c> for the category MUST advance once
|
|
/// per non-empty successful batch.
|
|
/// </summary>
|
|
/// <exception cref="ServiceResultException">Thrown for method-level
|
|
/// failures: unknown category (<c>BadNodeIdUnknown</c>), category
|
|
/// does not support adds (<c>BadNotSupported</c>), or unauthorized
|
|
/// (<c>BadUserAccessDenied</c>).</exception>
|
|
ValueTask<StatusCode[]> AddAliasesAsync(
|
|
NodeId categoryId,
|
|
IReadOnlyList<AliasAddRequest> requests,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Implements <c>DeleteAliasesFromCategory</c> (Part 17 §6.3.5) —
|
|
/// removes each request as a single alias-to-target mapping from
|
|
/// the named category. Returns one <see cref="StatusCode"/> per
|
|
/// request; typical per-entry failures are <c>BadNotFound</c> when
|
|
/// the mapping is absent. <c>LastChange</c> for the category MUST
|
|
/// advance once per non-empty successful batch.
|
|
/// </summary>
|
|
/// <exception cref="ServiceResultException">Thrown for method-level
|
|
/// failures: unknown category (<c>BadNodeIdUnknown</c>), category
|
|
/// does not support deletes (<c>BadNotSupported</c>), or
|
|
/// unauthorized (<c>BadUserAccessDenied</c>).</exception>
|
|
ValueTask<StatusCode[]> DeleteAliasesAsync(
|
|
NodeId categoryId,
|
|
IReadOnlyList<AliasDeleteRequest> requests,
|
|
CancellationToken ct = default);
|
|
}
|
|
}
|