Skip to content

OPC UA Part 3 compliance: permissions, Write semantics, NodeManagement, NodeSet DataTypes, locales - #4675

Merged
romanett merged 23 commits into
masterfrom
romanett/opc-ua-part-3-compliance-21cf2e
Oct 5, 2026
Merged

romanett merged 23 commits into
masterfrom
romanett/opc-ua-part-3-compliance-21cf2e

Conversation

@romanett

@romanett romanett commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes the High and Medium findings of an OPC UA Part 3 (OPC 10000-3, Address Space Model) compliance audit. Each finding was checked against the full spec text (Parts 3, 4, 5, 6, 14 and 18) before it was fixed. Every fix comes with regression tests that fail without it.

Role permissions (Part 3 §8.55, Part 4 §5.12.2.2)

  • The type-hierarchy exemption in MasterNodeManager.ValidateRolePermissions / ValidateAccessRestrictions now covers only Browse, Read and ReadRolePermissions. Type nodes stay visible to generic clients, which was the intent of fix address space issues #3705; every other permission bit is now enforced on them.
  • ReceiveEvents is enforced on the EventType node. The standard NodeSet grants it on AuditEventType and its subtypes only to SecurityAdmin, so only SecurityAdmin sessions now receive audit events.
  • Call checks the Call permission and AccessRestrictions on the Object as well as on the Method.
  • When a Call's methodId is the Method declaration of the ObjectType, the Object's Method with the same BrowseName is permission-checked and invoked. The declaration is used only if the Object has no such Method. This applies to AsyncCustomNodeManager, CustomNodeManager2 and the sample node manager.

Write service (Part 3 §5.6.2, §8.40, §8.57; Part 4 §5.11.4)

  • The synchronous OnWriteValue path (fluent OnWrite) checks DataType and ValueRank first (Bad_TypeMismatch). After an IndexRange write it merges the slice into the cached value instead of replacing it.
  • StatusWrite / TimestampWrite are enforced (Bad_WriteNotSupported).
  • Undefined enumeration values and invalid OptionSet writes return Bad_OutOfRange. OptionSet writes merge only the valid bits into the stored value.
  • New ReportPropertyValueChanged / ReportSemanticChange let server code raise SemanticChangeEvents for changes it makes itself.
  • Required follow-up (Part 14 §6.2.11.1): if an external target rejects a timestamp, the PubSub subscriber's TargetVariables writer retries without it.

NodeManagement and Call arguments (Part 3 §5.5.2, §5.7.3, §6.4.2; Part 4 §5.8, §5.12.2)

  • AddNodes creates the Mandatory InstanceDeclarations of the TypeDefinition and rejects abstract or Interface types (Bad_TypeDefinitionInvalid).
  • AddReferences, DeleteReferences and DeleteNodes bump NodeVersion and raise GeneralModelChangeEvents.
  • Clients may omit trailing optional input arguments (HasOptionalInputArgumentDescription).

NodeSet DataTypes (Part 3 §5.8.3, §8.48; Part 6 Annex F.12)

  • UANodeSet.Import completes structure definitions with the base type's fields (base fields first). It creates definitions for structures without fields and sets DefaultEncodingId. StructureDefinition.FirstExplicitFieldIndex marks the subtype's own fields.
  • DataTypeState no longer mutates the stored definition on read.

Locales (Part 3 §5.2.5, §8.5; Part 4 §5.4)

  • Read, Browse and data change notifications return the translation that best matches the session's LocaleIds, or all translations for mul. The selection lives in the server dispatcher and is built on ResourceManager: the new TranslateValue calls the overridable Translate.
  • Locale ids are matched case-insensitively, and fallback translations carry their real locale.

Client complex types (Part 6 §5.1.7)

  • The Reflection.Emit builder encodes only IsOptional subtyped fields as ExtensionObject. It also supports OptionSet subtypes: an unsupported type is skipped instead of aborting the whole load.
  • TypeInfo.GetXmlName uses the data type's XML name for dynamic structures and OptionSets (the XML body element was <Structure>). Both builders now produce identical XML.

Behaviour changes

All are documented in docs/MigrationGuide.md.

  • Audit events: only SecurityAdmin receives them. The CTT "Auditing Connections" unit needs a SecurityAdmin user (e.g. sysadmin); AuditingOperationTests was updated.
  • Write timestamps and status: clients writing a SourceTimestamp or a non-Good StatusCode get Bad_WriteNotSupported unless the Variable has StatusWrite/TimestampWrite. No variable in src/ sets these bits.
  • AddNodes: it rejects abstract types (including BaseVariableType and BaseEventType), and new instances now come with child nodes.
  • Call: the Object needs the Call permission. The Object's own Method runs even when the call names the type declaration.

Not included

  • Low-severity findings: missing validation on optional services and author-side NodeSet rules.
  • LocalizedText nested inside structures or Variant arrays, and in HistoryRead or Call outputs.

Testing

net10.0, Debug, one suite at a time on the merged branch. All suites below pass:

Suite Passed
Types 11430
Core.Encoders 2947
Core.Schema 179
Core.Security 558
Client 2761
Server 8210
InformationModel 1080
Features 145
History 536
Di 455
Machinery 167
Robotics 661
Robotics.Intent 22
Pumps 75
Scales 122
Positioning 69
ISA95 144
Vision 484
PubSub 1490
PubSub.Adapter 151
PubSub.Server 177
Redundancy.Server 856
Lds 173
Configuration 241
WotCon 1496
  • Client.ComplexTypes: one test wrote a SourceTimestamp to a variable without TimestampWrite. It is fixed in this PR, and the suite has not been re-run since.
  • Core: 6068 passed, 1 failure in ActivityTraceFlags_Metrics_ShouldRecordMetricsOnly. That telemetry test passed when re-run alone, so it looks flaky.
  • Still running: Sessions, Subscriptions (Classic, Durable, standard) and Interop. Results will follow on this PR.
  • UA.slnx builds with 0 errors for net10.0 and net48, with no new analyzer warnings in changed files.

Related Issues

  • Part 3 compliance audit (no tracking issue)

Checklist

  • I have signed the CLA and read the CONTRIBUTING doc.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added all necessary documentation.
  • I have verified that my changes do not introduce (new) build or analyzer warnings.
  • I ran all tests locally using the UA.slnx solution against at least .net framework and .net 10, and all passed.
  • I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings.
  • I have addressed all PR feedback received.

🤖 Generated with Claude Code

romanett and others added 19 commits October 5, 2026 08:06
…, Browse and data changes

OPC 10000-4 5.4 / OPC 10000-3 5.2.5, 8.5: the server returns the most preferred
translation for the session's LocaleIds, all translations for 'mul'.

- ResourceManager.TranslateValue (virtual) translates scalar and array
  LocalizedText values; used centrally by the service dispatcher for Read
  results and Browse/BrowseNext target DisplayNames, by MonitoredItem for
  data change notifications and event fields. Stored node values unchanged.
- Locale ids compared case-insensitively (RFC 5646) in ResourceManager and
  LocalizedText.FilterByPreferredLocales; mul+locales falls back to another
  region of the requested language.
- ResourceManager labels a region fallback with its real locale and no longer
  returns all translations for mul+locales when only the count matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ct in the Reflection.Emit builder and support OptionSet subtypes

OPC 10000-6 5.1.7: in a StructureWithSubtypedValues or UnionWithSubtypedValues
only a field with IsOptional=TRUE allows subtypes and is serialized as an
ExtensionObject; a field with IsOptional=FALSE is encoded directly. The
Reflection.Emit builder encoded every structure field of such types as an
ExtensionObject, unlike the default builder.

- AttributeExtensions: use ExtensionObject only for IsOptional fields; record
  the declared DataType of encodeable fields (StructureFieldAttribute.DataTypeId)
  so fields whose runtime type does not identify it (OptionSet subtypes) are
  written with the right type id.
- ComplexTypeFieldBuilder: an optional subtyped structure field is an
  IEncodeable property so it can hold a subtype (generated subtypes are
  unrelated classes); before, decoding a subtype value threw.
- ComplexTypeBuilder.AddOptionSetType: create the Structure-backed OptionSet
  subtype (Encoders.OptionSet) like the default builder instead of throwing;
  the factory lists it in GetTypes.
- ComplexTypeSystem: a type the builder cannot create as an OptionSet subtype
  is logged and skipped instead of aborting the whole load batch; its
  definition is cached only after the type was created.

Parity tests run binary, XML and JSON cases through both builders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a type

TypeInfo.GetXmlName(object, context) fell back to the CLR type name for
instances that describe their own data type (Encoders.Structure and its
Union/StructureWithOptionalFields subclasses, the dynamic OptionSet). Every
dynamically built structure was therefore written as <Structure> instead of
the data type's XML name required by OPC 10000-6 5.3.1.16. Prefer the
instance's IEncodeableType.XmlName when it is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…check Call on the Object (Part 3 8.55)

- The IsPartOfTypeHierarchy exemption in ValidateRolePermissions and
  ValidateAccessRestrictions now only covers Browse, Read and
  ReadRolePermissions, so type nodes stay discoverable. All other
  permissions are enforced on type nodes: ReceiveEvents on the EventType
  (Anonymous/AuthenticatedUser no longer receive AuditEventType-derived
  events; ns0 grants ReceiveEvents on audit types only to SecurityAdmin),
  Call on methods declared on types, Write, WriteRolePermissions,
  AddReference, RemoveReference and DeleteNode.
- Add a ValidateAccessRestrictions overload taking the requested
  permission; the dispatcher passes it through.
- Call checks the Call permission (and the AccessRestrictions in the
  dispatcher) on the Object passed as ObjectId as well as on the Method,
  in NodeManagerServiceDispatcher, AsyncCustomNodeManager and
  CustomNodeManager.
- AuditingOperationTests: audit delivery is asserted for a SecurityAdmin
  subscriber; new test that Anonymous and AuthenticatedUser subscribers
  receive RefreshStart/End events but no audit events.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- AddNodes creates the Mandatory InstanceDeclarations of the TypeDefinition
  and its supertypes (most derived declaration wins, Optional overrides hide
  inherited Mandatory ones), recursively completed from each child's own
  TypeDefinition, rebased onto per-instance NodeIds via the NodeId factory
  (Part 4 5.8.2.1, Part 3 6.4.2).
- AddNodes rejects abstract TypeDefinitions, BaseVariableType, BaseEventType
  and Interface types with Bad_TypeDefinitionInvalid (Part 3 5.5.2, 5.6.5,
  4.10.2).
- AddReferences/DeleteReferences record ReferenceAdded/ReferenceDeleted,
  which bumps the NodeVersion and raises a GeneralModelChangeEvent; DeleteNodes
  records ReferenceDeleted on the parent (Part 3 5.x, 9.32.2).
- MethodState.Call accepts omitted trailing optional input arguments declared
  by HasOptionalInputArgumentDescription and passes them as null Variants
  (Part 3 5.7.3, Part 4 5.12.2.2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The body element name of dynamic structures now follows the data type,
so the Reflection.Emit and default builders produce identical XML.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- BaseVariableState: verify DataType/ValueRank (Bad_TypeMismatch) before the
  synchronous OnWriteValue handler runs, as the async path already did.
- Merge an IndexRange slice into the cached value after OnWriteValue accepts
  it instead of storing the slice as the whole value.
- Enforce AccessLevel StatusWrite/TimestampWrite (Part 3 8.57): without the
  bits only StatusCode Good / a null SourceTimestamp may be written, else
  Bad_WriteNotSupported. Server-internal setters are not affected.
- Reject Int32 values that are not defined values of an Enumeration DataType
  registered with the encodeable factory (Bad_OutOfRange, Part 4 Table 55).
- OptionSet structure writes (Part 3 8.40): size and valid-bit checks
  (Bad_OutOfRange) and (value & validBits) | (current & ~validBits) merge in
  AsyncCustomNodeManager and CustomNodeManager2.
- Public ReportPropertyValueChanged / ReportSemanticChange on both node
  managers so server-side changes of semantic Properties raise a
  SemanticChangeEvent and set the SemanticsChanged bit (Part 3 5.6.2).
- Tests that wrote source timestamps/status codes to variables without the
  access bits now write null timestamps or set the bits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt 3 5.8.3, 8.48)

A NodeSet Definition lists only the fields a DataType adds (Part 6 F.12),
but the DataTypeDefinition attribute must start with the fields of the
baseDataType (Part 3 8.48). Runtime-loaded subtypes were served with their
own fields only, so clients decoded them with the wrong layout.

- UANodeSet.CompleteDataTypeDefinitions merges inherited fields after all
  DataTypes of an import are known: multi-level, order independent,
  cycle-guarded, idempotent (FirstExplicitFieldIndex records the inherited
  part; a definition that already lists the base fields is not extended).
  StructureType follows inherited optional/subtyped fields. It runs at the
  end of every UANodeSet.Import and again over the whole runtime/fluent
  import batch, where supertypes in other documents, other node managers
  and compiled types (encodeable factory) are resolved.
- A Structure subtype without own fields (no Field list) now gets a
  StructureDefinition instead of none.
- Enumeration vs structure is decided by the supertype (Enumeration,
  OptionSet flag, Structure/Union); the Value heuristic is a fallback.
- DefaultEncodingId of concrete structures is set from the Default Binary
  HasEncoding target at import; abstract types keep null.
- DataTypeState no longer mutates the stored StructureDefinition on read;
  the encodeable-factory DefaultEncodingId fill works on a copy and is
  skipped for abstract types.
- Field ArrayDimensions are parsed before the "first dimension > 0" check
  (was comparing a character) and dropped for scalars.
- NodeSet export writes only the own fields (from FirstExplicitFieldIndex).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cept them

Part 14 6.2.11.1: a received timestamp is not used when the target Variable
does not allow writing timestamps. Servers now reject timestamps without the
TimestampWrite access level bit (Part 3 8.57), so the external-server writer
retries a Bad_WriteNotSupported write without timestamps and remembers the
target. The StatusCode is kept so a target that cannot take it still reports
the failure to the reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… declaration

OPC 10000-4 5.12.2.2 allows the methodId of a Call to be the Method
declaration of the ObjectType, but the RolePermissions are always verified
with the Method that is the HasComponent target of the objectId. When the
Object's Method did not carry the declaration as MethodDeclarationId (for
example because a subtype overrides the Method), the node managers invoked
the declaration and checked its permissions instead. Resolve a declaration
found in the type hierarchy to the Object's Method with the same BrowseName
(child or HasComponent target) in AsyncCustomNodeManager, CustomNodeManager2
and the sample node manager; keep the declaration only when the Object has
no such Method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 5, 2026 08:25
The test variable has no TimestampWrite access level bit, so the server
rejects a SourceTimestamp with Bad_WriteNotSupported (OPC 10000-3 8.57).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved critical write-access and OptionSet issues, plus moderate correctness findings, block approval.

Review effort: Balanced
Findings: 2 High severity · 3 Medium severity · 1 Low severity

Open (6)
What changed in this PR

Addresses OPC UA Part 3 compliance across permissions, Write and Call semantics, NodeManagement, NodeSet DataTypes, localization, and client complex-type encoding.

Changes:

  • Tightens authorization and write validation; extends optional arguments and model-change reporting.
  • Completes inherited DataType definitions and adds session-locale selection.
  • Extends complex-type support and PubSub timestamp fallback, with regression tests and migration guidance.
File Changes and unresolved findings
tests/​Opc.Ua.Types.Tests/​State/​MethodStateTests.cs Tests omitted optional Call arguments.
tests/​Opc.Ua.Types.Tests/​State/​DataTypeStateTests.cs Tests nonmutating definition reads.
tests/​Opc.Ua.Types.Tests/​State/​BaseVariableStateWriteSemanticsTests.cs Tests write validation, access bits, and enumeration values.
tests/​Opc.Ua.Types.Tests/​State/​BaseVariableStateAsyncHooksTests.cs Updates async write tests for timestamp restrictions.
tests/​Opc.Ua.Types.Tests/​Encoders/​CodecReviewRegressionTests.cs Tests dynamic XML body names.
tests/​Opc.Ua.Types.Tests/​BuiltIn/​LocalizedTextTranslationTests.cs Tests locale matching and fallback.
tests/​Opc.Ua.Server.Tests/​TypeHierarchyPermissionTests.cs Tests type permissions, audit-event delivery, and Call authorization.
tests/​Opc.Ua.Server.Tests/​RuntimeNodeSet/​RuntimeNodeSetLifecycleTests.cs Updates inherited-definition rejection expectations.
tests/​Opc.Ua.Server.Tests/​RuntimeNodeSet/​RuntimeNodeSetDataTypeDefinitionTests.cs Tests cross-namespace structure inheritance.
tests/​Opc.Ua.Server.Tests/​ResourceManagerTests.cs Tests translation selection and localized values.
tests/​Opc.Ua.Server.Tests/​MonitoredItemTests.cs Tests localized notifications.
tests/​Opc.Ua.Server.Tests/​LocaleSelectionServiceTests.cs Tests Read and Browse localization.
tests/​Opc.Ua.Server.Tests/​Fluent/​TypedBuilderTests.cs Removes unsupported write timestamps.
tests/​Opc.Ua.Server.Tests/​Fluent/​NodeSetImportBuilderTests.cs Updates import-completion invocation.
tests/​Opc.Ua.Server.Tests/​CustomNodeManagerTests.cs Tests instance Method resolution.
tests/​Opc.Ua.Server.Tests/​ComplexTypes/​ServerComplexTypeSystemIntegrationTests.cs Tests structure-subtype round-trips.
tests/​Opc.Ua.Server.Tests/​ComplexTypes/​ServerComplexTypesTestServer.cs Initializes subtype fixture values.
tests/​Opc.Ua.Server.Tests/​ComplexTypes/​ServerComplexTypesTestNodeManager.cs Adds subtype fixture identifiers.
tests/​Opc.Ua.Server.Tests/​ComplexTypes/​ServerComplexTypesTestModel.NodeSet2.xml Defines subtype fixture nodes.
tests/​Opc.Ua.Server.Tests/​AsyncCustomNodeManagerTests.cs Tests semantic changes, OptionSets, and Call permissions.
tests/​Opc.Ua.Server.TestFramework/​ServerFixtureUtils.cs Supports session locale preferences.
tests/​Opc.Ua.PubSub.Adapter.Tests/​ServerTargetVariableWriterTests.cs Tests timestamp retries and status preservation.
tests/​Opc.Ua.InformationModel.Tests/​NodeManagementAddNodeTests.cs Tests mandatory members and abstract-type rejection.
tests/​Opc.Ua.Core.Tests/​Stack/​State/​NodeStateAttributeConcurrencyTests.cs Enables status and timestamp writes in fixtures.
tests/​Opc.Ua.Core.Tests/​Stack/​Schema/​UANodeSetDataTypeDefinitionTests.cs Tests definition completion and export.
tests/​Opc.Ua.Core.Security.Tests/​AuditingOperationTests.cs Tests role-restricted audit delivery.
tests/​Opc.Ua.Client.ComplexTypes.Tests/​TypeSystemClientTest.cs Removes an unsupported write timestamp.
tests/​Opc.Ua.Client.ComplexTypes.Tests/​Types/​MockResolver.cs Supports explicit DataType supertypes.
src/​Opc.Ua.Types/​State/​MethodState.cs Supports trailing optional arguments. Moderate, 2 votes (L907): mixed child/reference descriptions incorrectly reject omission of multiple optional arguments.
src/​Opc.Ua.Types/​State/​DataTypeState.cs Returns encoding information without mutating stored definitions.
src/​Opc.Ua.Types/​State/​BaseVariableState.cs Validates writes and merges slices. Critical, 1 vote (L1848): status/timestamp checks omit effective UserAccessLevel. Moderate, 3 votes (L1964): enumeration matrices bypass value validation. Moderate, 1 vote (L1712): callback replacements are not revalidated before caching. Moderate, 1 vote (L1848): Good-severity statuses with information bits incorrectly require StatusWrite.
src/​Opc.Ua.Types/​Schema/​UANodeSetHelpers.cs Integrates imported DataType definition completion.
src/​Opc.Ua.Types/​Schema/​UANodeSetDataTypeDefinitions.cs Completes inherited fields and encoding metadata.
src/​Opc.Ua.Types/​Internal/​StatusCodes.cs Adds internal BadOutOfRange support.
src/​Opc.Ua.Types/​BuiltIn/​TypeInfo.cs Uses dynamic DataType XML names.
src/​Opc.Ua.Types/​BuiltIn/​LocalizedText.cs Improves locale matching and fallback.
src/​Opc.Ua.Server/​Subscription/​MonitoredItem/​MonitoredItem.cs Localizes published values.
src/​Opc.Ua.Server/​RuntimeNodeSet/​RuntimeNodeSetNodeManager.cs Completes runtime DataType definitions.
src/​Opc.Ua.Server/​RuntimeNodeSet/​NodeSetImporter.cs Resolves external DataType supertypes.
src/​Opc.Ua.Server/​NodeManager/​ResourceManager.cs Adds value translation and locale fixes.
src/​Opc.Ua.Server/​NodeManager/​OptionSetWriteMerge.cs Validates and merges OptionSet writes. Critical, 1 vote (L70): arrays, matrices, and addressed IndexRange elements bypass validation and merging. Moderate, 1 vote (L69): cached UserAccessLevel ignores callback-granted write access. Moderate, 2 votes (L159): populated all-zero ValidBits masks are not preserved or enforced.
src/​Opc.Ua.Server/​NodeManager/​NodeManagerServiceDispatcher.cs Localizes responses and checks Object Call permissions.
src/​Opc.Ua.Server/​NodeManager/​MasterNodeManager.ServiceDispatch.cs Narrows type-hierarchy permission exemptions.
src/​Opc.Ua.Server/​NodeManager/​CustomNodeManagerAsync.cs Checks Object Call permissions.
src/​Opc.Ua.Server/​NodeManager/​CustomNodeManager.cs Resolves instance Methods and reports semantic changes.
src/​Opc.Ua.Server/​Fluent/​NodeManagerBuilder.cs Completes imported DataType definitions.
src/​Opc.Ua.Server/​Fluent/​FluentNodeManagerBase.cs Supplies server context for import completion.
src/​Opc.Ua.PubSub.Adapter/​Subscriber/​ServerTargetVariableWriter.cs Retries rejected timestamp writes. Moderate, 1 vote (L121): ServerTimestamp rejection can permanently suppress valid future SourceTimestamps.
src/​Opc.Ua.Core.Schema/​ComplexTypes/​ComplexTypeSystem.cs Skips unsupported OptionSet types. Moderate, 1 vote (L1078): the catch also suppresses resolver service/communication failures, allowing incomplete loads.
src/​Opc.Ua.Client.ComplexTypes/​Types/​StructureFieldAttribute.cs Adds declared field DataType metadata.
src/​Opc.Ua.Client.ComplexTypes/​Types/​ComplexTypePropertyInfo.cs Resolves field DataType identifiers. Moderate, 1 vote (L692): interpreting session-local namespace indices against another NamespaceTable can fail decoding or select the wrong type.
src/​Opc.Ua.Client.ComplexTypes/​TypeBuilder/​ComplexTypeFieldBuilder.cs Supports subtype-capable fields.
src/​Opc.Ua.Client.ComplexTypes/​TypeBuilder/​ComplexTypeFactory.cs Includes runtime-created types.
src/​Opc.Ua.Client.ComplexTypes/​TypeBuilder/​ComplexTypeBuilder.cs Creates runtime OptionSet types.
src/​Opc.Ua.Client.ComplexTypes/​TypeBuilder/​AttributeExtensions.cs Emits field encoding metadata. Moderate, 1 vote (L197): session-local OptionSet DataType identifiers need URI-qualified ExpandedNodeIds for reuse across NamespaceTables.
src/​Opc.Ua.Client.ComplexTypes/​TypeBuilder/​AssemblyModule.cs Tracks runtime-created types. Nit, 3 votes (L99): both synchronization sites should use a dedicated System.Threading.Lock rather than the List.
samples/​Quickstarts.Servers/​SampleNodeManager/​SampleNodeManager.cs Resolves type declarations to instance Methods.
docs/​MigrationGuide.md Documents compliance-related behavior changes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Opc.Ua.Server/NodeManager/OptionSetWriteMerge.cs Outdated
Comment thread src/Opc.Ua.Types/State/BaseVariableState.cs Outdated
Comment thread src/Opc.Ua.Server/NodeManager/OptionSetWriteMerge.cs Outdated
Comment thread src/Opc.Ua.Types/State/BaseVariableState.cs
Comment thread src/Opc.Ua.Types/State/MethodState.cs Outdated
Comment thread src/Opc.Ua.Client.ComplexTypes/TypeBuilder/AssemblyModule.cs Outdated
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Code coverage

✅ Coverage gate passed.

Check Result Threshold
✅ Project line rate 88.55% (304589/343958 lines) >= 70.00%
✅ Project branch rate 78.95% >= 60.00%
✅ Patch coverage 91.90% (1010/1099 changed lines) >= 75.00% (> 100 changed lines)
ℹ️ Baseline delta (advisory) +14.95 pp 73.60% recorded
Uncovered changed lines
  • src/Opc.Ua.Server/NodeManager/CustomNodeManagerAsync.cs: 140
  • src/Opc.Ua.Server/NodeManager/OptionSetWriteMerge.cs: 144, 261, 299, 300, 302, 304, 307, 309, 311, 312, 314, 318, 358, 363, 376
  • src/Opc.Ua.Types/Schema/UANodeSetHelpers.cs: 2450, 2451, 2452, 2453, 2454
  • src/Opc.Ua.Types/State/MethodState.cs: 860
  • src/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs: 155, 156
  • src/Opc.Ua.Server/RuntimeNodeSet/NodeSetImporter.cs: 238, 324, 343, 344, 345, 346, 347, 348
  • src/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs: 195, 199
  • src/Opc.Ua.Server/NodeManager/AsyncCustomNodeManager.cs: 2754, 3016, 3025, 4119, 4124, 4125, 4126, 4128, 4133, 5440, 5464, 5515, 5568
  • src/Opc.Ua.Server/NodeManager/CustomNodeManager.cs: 1674, 1679, 1680, 1681, 1683, 1688, 2967, 2991, 3042, 3095
  • src/Opc.Ua.Server/NodeManager/ResourceManager.cs: 557, 558
  • src/Opc.Ua.Types/Schema/UANodeSetDataTypeDefinitions.cs: 82, 86, 100, 120, 255, 321, 325, 329, 330, 335, 369, 386, 387, 388, 393, 404, 414, 443, 481
  • src/Opc.Ua.Types/State/BaseVariableState.cs: 1923, 1924, 1925, 1926, 1927, 1928, 1929, 1932, 1941, 2017, 2020

Coverage is above the recorded baseline - consider ratcheting coverage-thresholds.json.

Thresholds live in coverage-thresholds.json. Whole report before exclusions: line 87.68%, branch 78.15%.

@codecov

codecov Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.34941% with 172 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.77%. Comparing base (52f8520) to head (21b21ea).
⚠️ Report is 9 commits behind head on master.

Files with missing lines Patch % Lines
...c/Opc.Ua.Server/NodeManager/OptionSetWriteMerge.cs 72.18% 15 Missing and 22 partials ⚠️
...pc.Ua.Types/Schema/UANodeSetDataTypeDefinitions.cs 83.64% 19 Missing and 16 partials ⚠️
...pc.Ua.Server/NodeManager/AsyncCustomNodeManager.cs 84.83% 11 Missing and 16 partials ⚠️
src/Opc.Ua.Types/State/BaseVariableState.cs 84.76% 10 Missing and 6 partials ⚠️
src/Opc.Ua.Server/NodeManager/CustomNodeManager.cs 70.00% 8 Missing and 7 partials ⚠️
...rc/Opc.Ua.Server/RuntimeNodeSet/NodeSetImporter.cs 81.69% 8 Missing and 5 partials ⚠️
src/Opc.Ua.Types/Schema/UANodeSetHelpers.cs 77.41% 5 Missing and 2 partials ⚠️
src/Opc.Ua.Types/State/MethodState.cs 88.46% 1 Missing and 5 partials ⚠️
...ent.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs 69.23% 2 Missing and 2 partials ⚠️
src/Opc.Ua.Server/NodeManager/ResourceManager.cs 91.17% 2 Missing and 1 partial ⚠️
... and 6 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4675      +/-   ##
==========================================
+ Coverage   82.60%   82.77%   +0.17%     
==========================================
  Files        2204     2257      +53     
  Lines      338604   343958    +5354     
  Branches    59030    59955     +925     
==========================================
+ Hits       279700   284711    +5011     
- Misses      39091    39203     +112     
- Partials    19813    20044     +231     
Flag Coverage Δ
actions 82.77% <84.34%> (+0.17%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
....Client.ComplexTypes/TypeBuilder/AssemblyModule.cs 100.00% <100.00%> (ø)
...ent.ComplexTypes/TypeBuilder/ComplexTypeFactory.cs 100.00% <100.00%> (ø)
...ient.ComplexTypes/Types/ComplexTypePropertyInfo.cs 95.46% <100.00%> (+0.02%) ⬆️
...ient.ComplexTypes/Types/StructureFieldAttribute.cs 100.00% <ø> (ø)
...c.Ua.Core.Schema/ComplexTypes/ComplexTypeSystem.cs 80.94% <100.00%> (+1.65%) ⬆️
src/Opc.Ua.Server/Fluent/FluentNodeManagerBase.cs 86.05% <100.00%> (+0.02%) ⬆️
src/Opc.Ua.Server/Fluent/NodeManagerBuilder.cs 77.11% <100.00%> (+0.14%) ⬆️
...r/NodeManager/MasterNodeManager.ServiceDispatch.cs 78.31% <100.00%> (+0.21%) ⬆️
...Server/NodeManager/NodeManagerServiceDispatcher.cs 87.23% <100.00%> (+0.22%) ⬆️
...Server/RuntimeNodeSet/RuntimeNodeSetNodeManager.cs 78.33% <100.00%> (+0.31%) ⬆️
... and 19 more

... and 105 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- OptionSet writes (Part 3 8.40): validate and merge every element of an
  OptionSet array or matrix, including elements addressed by an IndexRange,
  against the stored element at the same position. A populated all-zero
  ValidBits of the stored value means no bit is valid and is preserved; the
  DataType definition is used only when the stored mask is absent. The
  access guard uses the effective UserAccessLevel (OnReadUserAccessLevel),
  so callback-granted write access no longer bypasses the validation.
- StatusWrite/TimestampWrite (Part 3 8.57) are also required in the
  effective UserAccessLevel (Bad_UserAccessDenied otherwise). "Good" is
  compared by the code bits only (Part 4 7.38.1: the low 16 bits do not
  affect the meaning of the StatusCode), so Good with info bits needs no
  StatusWrite.
- Enumeration range check covers matrices.
- Optional input arguments: trailing arguments consume unresolved
  HasOptionalInputArgumentDescription references, so child and
  bare-reference descriptions can be mixed.
- AssemblyModule locks on a dedicated Lock instead of the List.
- PubSub ServerTargetVariableWriter retries first without the
  ServerTimestamp and remembers rejected timestamp kinds separately, so a
  rejected ServerTimestamp does not suppress SourceTimestamps.
- ComplexTypeSystem only skips an OptionSet subtype on NotSupported /
  DataTypeNotSupported; other failures propagate as before.
- StructureFieldAttribute.DataTypeId stores the namespace-uri qualified
  TypeId, independent of the namespace table of the building session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romanett

romanett commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review-overview findings that have no inline thread in 9d0a22b:

  • Cached UserAccessLevel ignores callback-granted write access (OptionSetWriteMerge): fixed. The OptionSet check now resolves the UserAccessLevel through OnReadUserAccessLevel (WriteOptionSetValidatesWhenCallbackGrantsUserWriteAsync).
  • ServerTimestamp rejection suppresses future SourceTimestamps (ServerTargetVariableWriter): fixed. Rejected timestamps are remembered per kind, and the writer retries without the ServerTimestamp first (WriteAsyncRejectedServerTimestampKeepsSourceTimestampAsync).
  • Catch swallows resolver failures (ComplexTypeSystem): fixed. The catch is narrowed to NotSupportedException / DataTypeNotSupportedException, so service and communication errors propagate as before (OptionSetSubtypeFailureOtherThanNotSupportedIsNotSwallowedAsync).
  • Session-local DataTypeIds (ComplexTypePropertyInfo / AttributeExtensions): fixed. The emitter writes the namespace-URI-qualified TypeId, which is parsed without a namespace table (OptionSetSubtypeFieldDataTypeIdIsIndependentOfNamespaceTableAsync).
  • OnWriteValue replacement not revalidated (BaseVariableState): intentionally unchanged. A value returned by the handler comes from trusted server code, like assigning Value directly. The PR adds the type check on the client-supplied value before the handler runs.

The MigrationGuide is updated: UserAccessLevel must carry StatusWrite/TimestampWrite, Good with info bits counts as Good, and the OptionSet array and all-zero ValidBits rules are described.

🤖 Addressed by Claude Code

romanett and others added 2 commits October 5, 2026 11:54
…e variables

The reference server accepts a StatusCode and SourceTimestamp written together
with the Value (CTT "Attribute Write StatusCode & Timestamp", Data Access
PercentDeadband, Monitor Value Change V2). OPC 10000-3 8.57 now gates those
writes on the StatusWrite / TimestampWrite AccessLevel bits, so add them to
every data variable that grants CurrentWrite (AccessLevel and UserAccessLevel
separately, keeping user restrictions and the AccessLevelEx bits).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bits

The fluent Configure step assigned AccessLevelEx on the two non-atomic
variables after StatusWrite/TimestampWrite were added, which reset the low
byte of AccessLevel while UserAccessLevel kept the new bits. Add the
non-atomic bits instead of replacing the value and update
ReferenceNodeManagerNonatomicAccessLevelsAreConsistentAsync for the new
access bits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romanett
romanett enabled auto-merge (squash) October 5, 2026 11:56
@romanett
romanett merged commit 7212607 into master Oct 5, 2026
178 checks passed
@romanett
romanett deleted the romanett/opc-ua-part-3-compliance-21cf2e branch October 7, 2026 03:48
marcschier pushed a commit that referenced this pull request Oct 7, 2026
…dAuditEvents; document CTT defect C57 (#4693)

## Description

CTT 1.05.513 (patched scripts) GDS Application Directory `011.js`,
`018.js` and `028.js` fail on master (fa06c66) with "Step 3: Did not
receive any Events after calling Publish." and passed on efad0dd.

### Root cause
7212607 (#4675, Part 3 compliance), not #4668. Since #4675 the server
enforces `ReceiveEvents` on the EventType node (OPC 10000-3 §8.55: a
Client only receives an Event if it has ReceiveEvents on its EventType
and SourceNode). The GDS NodeSet grants it on
`ApplicationRegistrationChangedAuditEventType`
(`AuditEventDefaultPermissions`) only to **SecurityAdmin**. The CTT test
cases subscribe on the Session of the GDS `AdministrativeUser`, a
**DiscoveryAdmin**, so the server correctly withholds the events. The
audit events are still raised and delivered: the RegisterApplication
change to `OnCallAsync` in #4668 does not affect them.

This is a CTT defect, so the server is unchanged. It is documented as
**C57** in `plans/ctt-issues.md` and filed as [Mantis
11482](https://mantis.opcfoundation.org/view.php?id=11482). The fix is
for the scripts to subscribe on a second Session of the CU's
`SecurityAdminUser`.

### Changes
- `tests/Opc.Ua.Gds.Tests/GdsApplicationRegistrationAuditEventTests.cs`
(new): a DiscoveryAdmin calls RegisterApplication, UpdateApplication and
UnregisterApplication. A SecurityAdmin subscriber on
`Server.EventNotifier` must receive all three
`ApplicationRegistrationChangedAuditEvent`s with `SourceNode` =
Directory and the matching `MethodId`. A DiscoveryAdmin subscriber must
receive none of them.
- `plans/ctt-issues.md`: C57 (Mantis 11482).

## Verification
- New test passes on net10.0 (~1 s); the test project builds on net48.
- CTT p14-gds against an unchanged master server (fa06c66) with the
C57 script patch (second Session as `sysadmin` for the event
subscription), compared with efad0dd using the unpatched scripts:
  - Application Directory 011/018/028: Error → **Pass**.
  - AliasName Discovery 001 Pass, 002 Warning, 004 Pass.
- Only other differences: two response-timestamp timing Warnings (App
Directory 023 ActivateSession and Query Applications 002 CreateSession,
> 100 ms).
- CTT p12-auditing against fa06c66: identical to the baseline (21
Pass, 5 Skipped, 81 NotImpl, 0 errors).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants