fix(opcua): PopulateServerArray writes IServerInternal.ServerUris so clients see peers
This commit is contained in:
@@ -154,23 +154,57 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the union of <see cref="OpcUaApplicationHostOptions.ApplicationUri"/> and
|
||||
/// <see cref="OpcUaApplicationHostOptions.PeerApplicationUris"/> to the OPC UA standard
|
||||
/// <c>Server.ServerArray</c> property (NodeId i=2254). Clients in a warm-redundancy
|
||||
/// deployment discover the partner endpoint by reading this property.
|
||||
/// Publishes <see cref="OpcUaApplicationHostOptions.PeerApplicationUris"/> via the OPC UA
|
||||
/// standard <c>Server.ServerArray</c> property (NodeId i=2254) so warm-redundancy clients
|
||||
/// can discover the partner endpoint.
|
||||
///
|
||||
/// The wire-served value of <c>Server.ServerArray</c> comes from
|
||||
/// <see cref="IServerInternal.ServerUris"/> (an <see cref="Opc.Ua.StringTable"/>) via the
|
||||
/// SDK's <c>OnReadServerArray</c> callback — writes to
|
||||
/// <c>ServerObject.ServerArray.Value</c> are NOT what clients read. The SDK auto-populates
|
||||
/// slot 0 with the local <c>ApplicationUri</c> on <c>ApplicationInstance.Start</c>; we
|
||||
/// append the configured peers at slots 1, 2, … here.
|
||||
///
|
||||
/// The address-space property is also mirrored for in-process readers (the unit-test
|
||||
/// observation seam) and as a defensive belt-and-braces measure.
|
||||
/// </summary>
|
||||
private void PopulateServerArray()
|
||||
{
|
||||
var serverObject = _server?.CurrentInstance?.ServerObject;
|
||||
if (serverObject is null) return;
|
||||
var internalData = _server?.CurrentInstance;
|
||||
if (internalData is null) return;
|
||||
|
||||
// Wire path: append peers to IServerInternal.ServerUris — this is what
|
||||
// OnReadServerArray serves to remote clients reading VariableIds.Server_ServerArray.
|
||||
var serverUris = internalData.ServerUris;
|
||||
var existing = new HashSet<string>(StringComparer.Ordinal);
|
||||
for (uint i = 0; i < (uint)serverUris.Count; i++)
|
||||
{
|
||||
var existingUri = serverUris.GetString(i);
|
||||
if (existingUri is not null) existing.Add(existingUri);
|
||||
}
|
||||
|
||||
var uris = new List<string> { _options.ApplicationUri };
|
||||
foreach (var peer in _options.PeerApplicationUris)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(peer) && !uris.Contains(peer))
|
||||
uris.Add(peer);
|
||||
if (string.IsNullOrWhiteSpace(peer)) continue;
|
||||
if (existing.Contains(peer)) continue;
|
||||
serverUris.Append(peer);
|
||||
existing.Add(peer);
|
||||
}
|
||||
|
||||
// In-process mirror: ServerObject.ServerArray.Value is consulted by some tests and
|
||||
// tooling that read the SDK's address-space model directly rather than going through
|
||||
// a session. Harmless on the wire (the SDK ignores it) but useful in-VM.
|
||||
var serverObject = internalData.ServerObject;
|
||||
if (serverObject is not null)
|
||||
{
|
||||
var uris = new List<string> { _options.ApplicationUri };
|
||||
foreach (var peer in _options.PeerApplicationUris)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(peer) && !uris.Contains(peer))
|
||||
uris.Add(peer);
|
||||
}
|
||||
serverObject.ServerArray.Value = uris.ToArray();
|
||||
}
|
||||
serverObject.ServerArray.Value = uris.ToArray();
|
||||
}
|
||||
|
||||
private void OnImpersonateUser(Session session, ImpersonateEventArgs args) =>
|
||||
|
||||
Reference in New Issue
Block a user