Files
Marc Schier 81f200c9d2 Align WoT conversion and runtime with Binding 1.1 (#4375)
## Summary

Align the generic WoT/NodeSet converter, WoT Connectivity registry, and
runtime
binding implementation with the OPC UA WoT Binding 1.1 draft.

### Conversion and preservation

- fix the standard placeholder ModellingRule NodeIds;
- make every produced/restored NodeSet importable by completing required
aliases;
- support `ua:ComponentOf`, arbitrary companion ReferenceTypes,
BrowseNames,
  InverseNames, symmetric references, and exact direction;
- enforce the projection annotation allowlist and deterministic total
order;
- emit and consume Method argument schemas, EventType data, Alarms &
Conditions,
  event severity, engineering units/ranges, localization, ValueRank, and
  ArrayDimensions;
- preserve unsupported content through JSON residue/native projection
rather than
  silently dropping it;
- compare residue conflicts with RFC 8785 canonical equality while
retaining
  byte-exact residue digests;
- stamp generated documents with `uav:bindingVersion: "1.1"`.

### Runtime bindings

- resolve an EventType Thing Model referenced with `tm:ref`, recursively
derive
  its `data` fields as the baseline selection, and overlay explicit
  `uav:eventSelectClauses` that also reference EventType TMs;
- pre-resolve EventType links asynchronously through the bounded
sibling-document
  resolver before synchronous protocol planning;
- retain read compatibility with legacy implementation-specific
`uav:eventFields`;
- expose nested event `data` matching the TD while retaining the flat
transport
  field index for compatibility;
- apply minimum-security constraints through deterministic endpoint
selection and
  fail-closed session verification;
- use one Unicode code-point comparer for projection membership and
endpoint ties.

### Validation and fixtures

- add strict, profile-aware validation while keeping default parsing
permissive and
  residue-preserving;
- pin 27 normative examples byte-for-byte to the specification
repository, with an
  offline SHA-256 manifest and regeneration test;
- regenerate checked-in WoT documents with the final canonical writer
and binding
  version;
- document the generic converter separately from the legacy OPC 10100-1
asset path.

## Notable compatibility changes

- converted NodeSets may gain a completed `<Aliases>` table so standard
  ReferenceType names can be imported;
- previously inverted/dangling placeholder ModellingRules are corrected;
- generated documents gain binding-version, localization,
rank/dimension, and exact
  ReferenceType metadata;
- event data now has the nested shape described by the binding;
- the former NodeId-based `uav:typeDefinitionId` clause is removed;
event
  selections now use EventType-TM `tm:ref` links;
- `uav:eventFields` remains readable but new documents use
  `uav:eventSelectClauses`;
- unknown well-formed future revision/profile claims produce unsupported
warnings
  rather than strict rejection.

## Generated files

Most of the 621-file diff is deterministic regeneration: 517 checked-in
WoT
documents gain the binding version and final canonical spelling.
Regeneration was
run twice and is byte-idempotent.

## Validation

- `dotnet restore UA.slnx`;
- `dotnet build UA.slnx -c Release` across all configured TFMs;
- `Opc.Ua.Types.Tests`: 9,215 net10.0; 9,208 net48; 9,208 net472;
- `Opc.Ua.WotCon.Bindings.Tests`: 611 each on net10.0, net9.0, and
net8.0;
- `Opc.Ua.WotCon.Tests`: 1,136 net10.0;
- `Opc.Ua.WotCon.Samples.Tests`: 14 net10.0;
- NativeAOT test executable: 136/136;
- all 27 strict fixture conversion/import checks;
- formatting, analyzers, `git diff --check`, and regeneration
idempotence.

The full net48 run retains one unrelated existing MAX_PATH failure in

`FileWotRegistryStoreTests.MultiLevelDirectoryCreationSyncsEveryModifiedParentInOrder`.
Solution-wide stress runs also include an intentional 60-minute soak and
unrelated
locale-sensitive/flaky tests; all affected WoT projects and tests pass.

## Specification

Depends on OPCF-Members/spec-drafts#31.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6a9f41b1-72f0-4c74-bd7f-26fa531e5738
2026-09-02 00:09:36 +00:00

227 lines
11 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/
* ======================================================================*/
using System.Collections.Generic;
namespace Opc.Ua.Server.Tests.CoverageNodeSet
{
/// <summary>
/// The expected node / attribute / reference catalogue authored in
/// <c>Opc.Ua.CoverageTest.NodeSet2.xml</c>. The same catalogue drives the
/// data-driven assertion battery for both the source-generation server and
/// the runtime-import server so that any attribute or reference dropped by
/// either pipeline fails a test.
/// </summary>
public static class CoverageTestCatalogue
{
/// <summary>
/// The model namespace URI.
/// </summary>
public const string NamespaceUri = "http://opcfoundation.org/UA/CoverageTest/";
/// <summary>
/// The secondary (dependent) model namespace URI.
/// </summary>
public const string SecondaryNamespaceUri =
"http://opcfoundation.org/UA/CoverageTest/Secondary/";
/// <summary>
/// An expected node: its numeric identifier in the model namespace,
/// its BrowseName, and its NodeClass.
/// </summary>
public sealed record ExpectedNode(uint Id, string BrowseName, NodeClass NodeClass);
/// <summary>
/// An expected reference edge. <paramref name="Target"/> is either a
/// model-namespace identifier (when <paramref name="TargetIsOwned"/>)
/// or an absolute standard <see cref="NodeId"/> numeric identifier in
/// namespace 0.
/// </summary>
public sealed record ExpectedReference(
uint Source,
uint ReferenceType,
uint Target,
bool TargetIsOwned,
bool IsForward);
/// <summary>
/// Every node authored in the model namespace.
/// </summary>
public static IReadOnlyList<ExpectedNode> Nodes { get; } =
[
// Reference types.
new(5001, "CoverageHierarchicalReference", NodeClass.ReferenceType),
new(5002, "CoverageSymmetricReference", NodeClass.ReferenceType),
new(5003, "CoverageAbstractReference", NodeClass.ReferenceType),
new(5004, "CoverageConcreteReference", NodeClass.ReferenceType),
// Data types + members + encodings.
new(5100, "CoverageEnumeration", NodeClass.DataType),
new(5101, "EnumValues", NodeClass.Variable),
new(5110, "CoverageOptionSet", NodeClass.DataType),
new(5111, "OptionSetValues", NodeClass.Variable),
new(5120, "CoverageAbstractStructure", NodeClass.DataType),
new(5130, "CoverageStructure", NodeClass.DataType),
new(5131, "Default Binary", NodeClass.Object),
new(5132, "Default XML", NodeClass.Object),
new(5133, "Default JSON", NodeClass.Object),
new(5140, "CoverageOptionalStructure", NodeClass.DataType),
new(5141, "Default Binary", NodeClass.Object),
new(5150, "CoverageUnion", NodeClass.DataType),
new(5151, "Default Binary", NodeClass.Object),
new(5160, "CoveragePlainStructure", NodeClass.DataType),
new(5161, "Default Binary", NodeClass.Object),
// Object / variable / event / interface types.
new(5200, "CoverageAbstractObjectType", NodeClass.ObjectType),
new(5210, "CoverageObjectType", NodeClass.ObjectType),
new(5211, "MandatoryVariable", NodeClass.Variable),
new(5212, "OptionalVariable", NodeClass.Variable),
new(5213, "MandatoryPlaceholder", NodeClass.Object),
new(5214, "OptionalPlaceholder", NodeClass.Object),
new(5215, "ExposesArrayVariable", NodeClass.Variable),
new(5216, "MandatoryMethod", NodeClass.Method),
new(5217, "InputArguments", NodeClass.Variable),
new(5218, "OutputArguments", NodeClass.Variable),
new(5230, "CoverageEventType", NodeClass.ObjectType),
new(5240, "CoverageInterfaceType", NodeClass.ObjectType),
new(5300, "CoverageVariableType", NodeClass.VariableType),
// Instances.
new(5400, "CoverageRoot", NodeClass.Object),
new(5402, "TypedObject", NodeClass.Object),
new(5403, "MandatoryVariable", NodeClass.Variable),
new(5404, "MandatoryMethod", NodeClass.Method),
new(5405, "InputArguments", NodeClass.Variable),
new(5406, "OutputArguments", NodeClass.Variable),
new(5410, "EventSource", NodeClass.Object),
new(5411, "SubNotifier", NodeClass.Object),
new(5412, "CoverageCondition", NodeClass.Object),
new(5420, "BooleanValue", NodeClass.Variable),
new(5421, "Int32Value", NodeClass.Variable),
new(5422, "StringArrayValue", NodeClass.Variable),
new(5423, "DoubleMatrixValue", NodeClass.Variable),
new(5424, "DateTimeValue", NodeClass.Variable),
new(5425, "EnumerationValue", NodeClass.Variable),
new(5426, "OrderedValue", NodeClass.Variable),
new(5450, "AddNumbers", NodeClass.Method),
new(5451, "LockedMethod", NodeClass.Method),
new(5452, "InputArguments", NodeClass.Variable),
new(5453, "OutputArguments", NodeClass.Variable),
new(5460, "CoverageView", NodeClass.View),
new(5470, "CoverageStateMachine", NodeClass.Object),
new(5471, "StateA", NodeClass.Object),
new(5472, "StateB", NodeClass.Object),
new(5473, "TransitionAB", NodeClass.Object),
// Deep, branching instance tree.
new(5490, "TreeRoot", NodeClass.Object),
new(5491, "BranchA", NodeClass.Object),
new(5492, "LeafA1", NodeClass.Variable),
new(5493, "SubBranchA", NodeClass.Object),
new(5494, "LeafA2", NodeClass.Variable),
new(5495, "BranchB", NodeClass.Object),
new(5496, "LeafB1", NodeClass.Variable),
];
/// <summary>
/// Every node authored in the secondary (dependent) model namespace.
/// </summary>
public static IReadOnlyList<ExpectedNode> SecondaryNodes { get; } =
[
new(6001, "SecondaryObjectType", NodeClass.ObjectType),
new(6010, "SecondaryInstance", NodeClass.Object),
];
/// <summary>
/// At least one forward (or explicitly authored inverse) occurrence of
/// every reference relation the model exercises.
/// </summary>
public static IReadOnlyList<ExpectedReference> References { get; } =
[
// Hierarchical.
new(5400, ReferenceTypes.Organizes, Objects.ObjectsFolder, false, false),
new(5400, ReferenceTypes.HasComponent, 5424, true, true),
new(5400, ReferenceTypes.HasOrderedComponent, 5426, true, true),
new(5100, ReferenceTypes.HasProperty, 5101, true, true),
new(5200, ReferenceTypes.HasSubtype, 5210, true, true),
new(5410, ReferenceTypes.HasEventSource, 5411, true, true),
// Non-hierarchical / instance wiring.
new(5420, ReferenceTypes.HasTypeDefinition, VariableTypes.BaseDataVariableType, false, true),
new(5211, ReferenceTypes.HasModellingRule, ObjectIds_ModellingRule_Mandatory, false, true),
new(5212, ReferenceTypes.HasModellingRule, ObjectIds_ModellingRule_Optional, false, true),
new(5213, ReferenceTypes.HasModellingRule, ObjectIds_ModellingRule_MandatoryPlaceholder, false, true),
new(5214, ReferenceTypes.HasModellingRule, ObjectIds_ModellingRule_OptionalPlaceholder, false, true),
new(5215, ReferenceTypes.HasModellingRule, ObjectIds_ModellingRule_ExposesItsArray, false, true),
new(5130, ReferenceTypes.HasEncoding, 5131, true, true),
new(5210, ReferenceTypes.HasInterface, 5240, true, true),
new(5210, ReferenceTypes.GeneratesEvent, 5230, true, true),
new(5216, ReferenceTypes.AlwaysGeneratesEvent, 5230, true, true),
new(5410, ReferenceTypes.HasCondition, 5412, true, true),
// State machine.
new(5473, ReferenceTypes.FromState, 5471, true, true),
new(5473, ReferenceTypes.ToState, 5472, true, true),
new(5473, ReferenceTypes.HasCause, 5450, true, true),
new(5473, ReferenceTypes.HasEffect, 5230, true, true),
new(5471, ReferenceTypes.HasTrueSubState, 5472, true, true),
new(5471, ReferenceTypes.HasFalseSubState, 5472, true, true),
// Custom reference types + inverse authoring.
new(5400, 5001, 5420, true, true),
new(5400, 5002, 5421, true, true),
new(5400, 5004, 5422, true, true),
// Deep instance tree (nested HasComponent chain).
new(5400, ReferenceTypes.HasComponent, 5490, true, true),
new(5490, ReferenceTypes.HasComponent, 5491, true, true),
new(5491, ReferenceTypes.HasComponent, 5493, true, true),
new(5493, ReferenceTypes.HasComponent, 5494, true, true),
];
// Standard modelling-rule object identifiers (namespace 0). OPC 10000-5
// assigns OptionalPlaceholder 11508 and MandatoryPlaceholder 11510; the
// two are neither adjacent nor in name order, so they are easy to swap.
private const uint ObjectIds_ModellingRule_Mandatory = 78;
private const uint ObjectIds_ModellingRule_Optional = 80;
private const uint ObjectIds_ModellingRule_ExposesItsArray = 83;
private const uint ObjectIds_ModellingRule_OptionalPlaceholder = 11508;
private const uint ObjectIds_ModellingRule_MandatoryPlaceholder = 11510;
/// <summary>
/// Builds the model-namespace <see cref="NodeId"/> for an owned node.
/// </summary>
public static NodeId NodeId(uint id, ushort namespaceIndex)
{
return new NodeId(id, namespaceIndex);
}
}
}