Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/MigrationGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,30 @@ Looking for the broader narrative (non-prescriptive overview of what
changed in a release)? See
[What's New in 2.0](WhatsNewIn2.0.md).

## Migrating node types that override FindChild or CreateChild

`NodeState.FindChild` and `NodeState.CreateChild` take
`assignInstanceNodeIds` as their last parameter, and the four argument
`FindChild` / two argument `CreateChild` virtuals are gone. The parameter
defaults to `true`, so **call sites are unaffected**; an override fails to
compile (`CS0115`) until the parameter is added and passed on.

Behaviour note: a node copy — `NodeState.Create(context, source)` and the
`Initialize(ISystemContext, NodeState)` path behind it — now passes
`assignInstanceNodeIds: false`. It no longer asks
`ISystemContext.NodeIdFactory` for identifiers that the copy overwrites
from the source on the very next statement. If your `INodeIdFactory`
counts, reserves or audits every allocation, expect **fewer** calls than in
1.5.378 for the same address space; the resulting NodeIds are unchanged.
Any `NodeState` subclass you own must thread the argument into its
`CreateOrReplace<Child>` calls to get that benefit.

See
[Node states § FindChild and CreateChild](migrate/2.0.x/node-states.md#nodestate-findchild-and-createchild-state-nodeid-assignment)
for the before/after and
[Custom node types and assignment control](NodeManagers.md#custom-node-types-and-assignment-control)
for the runtime rules.

## Migrating servers that relied on unserved history advertisement

Server startup now reconciles variables that advertise history with the
Expand Down
45 changes: 45 additions & 0 deletions docs/NodeManagers.md
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,51 @@ Notes:
`ISystemContext.NodeIdFactory`. `AsyncCustomNodeManager` supplies one
that allocates from the manager's namespace; override `New` to derive
ids from the parent chain instead.
* **A node copy never assigns.** `NodeState.Create(context, source)`
initialises each child from its source right after creating it, which
overwrites any NodeId minted along the way — so minting one would only
consume identifiers, and leak them for factories that track outstanding
allocations. The copy therefore calls
`CreateChild(context, browseName, assignInstanceNodeIds: false)`.

#### Custom node types and assignment control

`NodeState.FindChild` and `NodeState.CreateChild` carry
`assignInstanceNodeIds` as their last parameter. It defaults to `true`, so
callers that state no intent keep materialising children with per-instance
NodeIds. A type that declares children overrides `FindChild`, resolves the
ones it declares, and passes the request on — both to its
`CreateOrReplace<Child>` helpers and to the base:

```csharp
protected override BaseInstanceState? FindChild(
ISystemContext context,
QualifiedName browseName,
bool createOrReplace,
BaseInstanceState? replacement,
bool assignInstanceNodeIds = true)
{
if (browseName.Name == BrowseNames.EnumStrings)
{
return !createOrReplace
? EnumStrings
: CreateOrReplaceEnumStrings(context, replacement, assignInstanceNodeIds);
}

return base.FindChild(
context, browseName, createOrReplace, replacement, assignInstanceNodeIds);
}
```

Source generated types emit exactly this shape. Because the request is an
argument, every type — generated or hand-written — sees the real
`ISystemContext` during a copy; nothing wraps the context to hide the
`NodeIdFactory`.

> **Breaking change in 2.0.** The four argument `FindChild` and the two
> argument `CreateChild` are gone. An override written against 1.5.378 fails
> to compile until the parameter is added; see the
> [migration guide](migrate/2.0.x/node-states.md#nodestate-findchild-and-createchild-state-nodeid-assignment).

### Current limitations

Expand Down
2 changes: 1 addition & 1 deletion docs/migrate/2.0.x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ table; loading a single sub-doc keeps the context window small.
| `OPCFoundation.NetStandard.Opc.Ua.*` package upgrade, TFM changes, Newtonsoft removal | [`packages.md`](packages.md) |
| Source-generated `*Collection` shims, NodeManager generator, default of boolean properties, project structure | [`source-generation.md`](source-generation.md) |
| `IEncodeableFactoryBuilder`, `IType`, JSON / XML / binary encoders, `EncodeableFactory.GlobalFactory`, `IJsonEncodeable`, `ComplexTypes` namespace move | [`encoders.md`](encoders.md) |
| `CustomNodeManager`, `NodeState` clone / read / write helpers, `OnAfterCreate(CancellationToken)`, `INodeManager3`, `INodeCache.InvalidateNode`, generics on `BaseVariableState` / `BaseVariableTypeState`, predefined-node processing | [`node-states.md`](node-states.md) |
| `CustomNodeManager`, `NodeState` clone / read / write helpers, `OnAfterCreate(CancellationToken)`, `FindChild` / `CreateChild` NodeId assignment, `INodeManager3`, `INodeCache.InvalidateNode`, generics on `BaseVariableState` / `BaseVariableTypeState`, predefined-node processing | [`node-states.md`](node-states.md) |
| `IUserIdentityTokenHandler`, `IClientIdentityProvider`, `IUserTokenAuthenticator`, `IAccessTokenProvider`, `ITokenIssuer`, `IIdentityClaims`, caller-supplied secrets, secret store | [`identity.md`](identity.md) |
| `CertificateValidator`, ref-counted `Certificate` wrapper, `CertificateManager`, `ICertificateProvider`, obsoleted `X509Certificate2` direct-exposure APIs, PushManagement transactions (`ApplyChanges`-gated TrustList updates) | [`certificates.md`](certificates.md) |
| `ApplicationConfiguration` changes, Data-Contract serializer removal, `ParseExtension` / `UpdateExtension` signature, session / browser state persistence | [`configuration.md`](configuration.md) |
Expand Down
59 changes: 59 additions & 0 deletions docs/migrate/2.0.x/node-states.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,65 @@ protected override void OnAfterCreate(ISystemContext context, NodeState node, Ca
}
```

#### NodeState FindChild and CreateChild state NodeId assignment

`NodeState.FindChild` and `NodeState.CreateChild` now take
`assignInstanceNodeIds` as their last parameter, and the old four argument
`FindChild` / two argument `CreateChild` virtuals are gone. The parameter
defaults to `true`, so **call sites keep compiling and keep the 1.5.378
behaviour** — only overrides have to change:

```csharp
// Before
protected override BaseInstanceState FindChild(
ISystemContext context,
QualifiedName browseName,
bool createOrReplace,
BaseInstanceState replacement)
{
if (browseName.Name == BrowseNames.MyChild)
{
return createOrReplace
? CreateOrReplaceMyChild(context, replacement)
: MyChild;
}
return base.FindChild(context, browseName, createOrReplace, replacement);
}

// After — add the parameter and pass it on
protected override BaseInstanceState FindChild(
ISystemContext context,
QualifiedName browseName,
bool createOrReplace,
BaseInstanceState replacement,
bool assignInstanceNodeIds = true)
{
if (browseName.Name == BrowseNames.MyChild)
{
return createOrReplace
? CreateOrReplaceMyChild(context, replacement, assignInstanceNodeIds)
: MyChild;
}
return base.FindChild(
context, browseName, createOrReplace, replacement, assignInstanceNodeIds);
}
```

A missing override raises `CS0115` (`no suitable method found to
override`), so the compiler points at every site that needs the parameter.
Repeat the `= true` default in the override so callers bound to your derived
type keep the same behaviour.

Why: a node copy creates each child and then initialises it from its source,
which overwrites any NodeId minted along the way. Passing
`assignInstanceNodeIds: false` — what `NodeState.Create(context, source)`
now does — stops the `ISystemContext.NodeIdFactory` from being asked for
identifiers that are immediately discarded, and leaked by factories that
track outstanding allocations. Thread the argument into every
`CreateOrReplace<Child>` call your override makes; source generated types
already do. See
[Custom node types and assignment control](../../NodeManagers.md#custom-node-types-and-assignment-control).

### INodeManager3 - new role-permission and method-resolution hooks

2.0 introduces `INodeManager3`, an extension of `INodeManager2` that surfaces explicit hooks for per-role permission evaluation and for resolving the target of a `Call` request. `CustomNodeManager2` implements the new members with safe defaults that mirror the previous behavior, so node managers that already derive from `CustomNodeManager2` need no changes.
Expand Down
24 changes: 9 additions & 15 deletions src/Opc.Ua.Types/State/BaseDataVariableState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,29 +165,23 @@ public override void GetChildren(ISystemContext context, IList<BaseInstanceState
base.GetChildren(context, children);
}

/// <summary>
/// Finds the child with the specified browse name.
/// </summary>
/// <inheritdoc/>
protected override BaseInstanceState? FindChild(
ISystemContext context,
QualifiedName browseName,
bool createOrReplace,
BaseInstanceState? replacement)
BaseInstanceState? replacement,
bool assignInstanceNodeIds = true)
{
if (browseName.IsNull)
if (browseName.Name == BrowseNames.EnumStrings)
{
return null;
return !createOrReplace
? EnumStrings
: CreateOrReplaceEnumStrings(context, replacement, assignInstanceNodeIds);
}

BaseInstanceState? instance = null;
switch (browseName.Name)
{
case BrowseNames.EnumStrings:
instance = !createOrReplace ?
EnumStrings : CreateOrReplaceEnumStrings(context, replacement);
break;
}
return instance ?? base.FindChild(context, browseName, createOrReplace, replacement);
return base.FindChild(
context, browseName, createOrReplace, replacement, assignInstanceNodeIds);
}

/// <summary>
Expand Down
9 changes: 3 additions & 6 deletions src/Opc.Ua.Types/State/ISystemContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,9 @@ public interface ISystemContext
/// A factory that can be used to create node ids.
/// </summary>
/// <value>
/// The node identifiers factory, or <c>null</c> when the context
/// suppresses NodeId assignment. Callers that assign NodeIds must
/// check for <c>null</c>; see
/// <see cref="NodeIdFactorySuppressedContext"/>, which a node copy
/// uses so materialising its children does not consume identifiers
/// the copy immediately overwrites.
/// The node identifiers factory, or <c>null</c> when the context does
/// not assign NodeIds. Callers that assign NodeIds must check for
/// <c>null</c>.
/// </value>
INodeIdFactory? NodeIdFactory { get; }

Expand Down
27 changes: 14 additions & 13 deletions src/Opc.Ua.Types/State/MethodState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -522,25 +522,26 @@ public override void GetChildren(ISystemContext context, IList<BaseInstanceState
ISystemContext context,
QualifiedName browseName,
bool createOrReplace,
BaseInstanceState? replacement)
BaseInstanceState? replacement,
bool assignInstanceNodeIds = true)
{
if (browseName.IsNull)
{
return null;
}
BaseInstanceState? instance = null;
switch (browseName.Name)
{
case BrowseNames.InputArguments:
instance = !createOrReplace ?
OutputArguments : CreateOrReplaceInputArguments(context, replacement);
break;
return !createOrReplace
? InputArguments
: CreateOrReplaceInputArguments(
context, replacement, assignInstanceNodeIds);
case BrowseNames.OutputArguments:
instance = !createOrReplace ?
OutputArguments : CreateOrReplaceOutputArguments(context, replacement);
break;
return !createOrReplace
? OutputArguments
: CreateOrReplaceOutputArguments(
context, replacement, assignInstanceNodeIds);
default:
return base.FindChild(
context, browseName, createOrReplace, replacement,
assignInstanceNodeIds);
}
return instance ?? base.FindChild(context, browseName, createOrReplace, replacement);
}

/// <summary>
Expand Down
97 changes: 0 additions & 97 deletions src/Opc.Ua.Types/State/NodeIdFactorySuppressedContext.cs

This file was deleted.

Loading
Loading