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
Original file line number Diff line number Diff line change
Expand Up @@ -5414,9 +5414,6 @@ protected override NodeState ValidateNode(
"Aggregates_String"
];

private static readonly bool[] s_booleanArray
= [true, false, true, false, true, false, true, false, true];

private static readonly double[] s_doubleArray =
[
9.00001d,
Expand All @@ -5435,23 +5432,6 @@ private static readonly float[] s_singleArray

private static readonly int[] s_int32Array = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19];

private static readonly string[] s_stringArray
= ["en", "fr", "de", "en", "fr", "de", "en", "fr", "de", "en"];

private static readonly string[] s_stringArray0 =
[
"a00",
"b10",
"c20",
"d30",
"e40",
"f50",
"g60",
"h70",
"i80",
"j90"
];

private static readonly string[] s_stringArray1 = ["open", "closed", "jammed"];
private static readonly string[] s_stringArray2 = ["red", "green", "blue", "cyan"];
private static readonly string[] s_stringArray3 = ["lolo", "lo", "normal", "hi", "hihi"];
Expand Down
42 changes: 34 additions & 8 deletions Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -490,17 +490,35 @@ public async Task DisconnectAsync(CancellationToken ct = default)
}
}

private async void Session_KeepAliveAsync(ISession session, KeepAliveEventArgs e)
private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
{
if (ServiceResult.IsBad(e.Status) && !m_disposed)
if (!ServiceResult.IsBad(e.Status) || m_disposed)
{
await m_lock.WaitAsync().ConfigureAwait(false);
return;
}

// Disposing the session waits for its keep-alive worker to stop, and
// this callback runs on that worker. Move the cleanup off the worker
// so the callback returns first, otherwise the dispose deadlocks while
// holding m_lock and every later call on this client hangs.
_ = Task.Run(async () =>
{
try
{
await m_lock.WaitAsync().ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
// the client was disposed, which also disposed the session.
return;
}

try
{
if (session == Session)
if (!m_disposed && session == Session)
{
Session.Dispose();
Session = null;
session.Dispose();
}
}
catch (Exception ex)
Expand All @@ -509,9 +527,17 @@ private async void Session_KeepAliveAsync(ISession session, KeepAliveEventArgs e
}
finally
{
m_lock.Release();
try
{
m_lock.Release();
}
catch (ObjectDisposedException)
{
// Dispose handed the lock to this task on release and
// then disposed it.
}
}
}
});
}

private void Session_SessionClosing(object sender, EventArgs e)
Expand Down Expand Up @@ -1560,7 +1586,7 @@ private async Task ConnectInternalAsync(
.ConfigureAwait(false);

Session.SessionClosing += Session_SessionClosing;
Session.KeepAlive += Session_KeepAliveAsync;
Session.KeepAlive += Session_KeepAlive;
Session.KeepAlive += KeepAlive;

// TODO: implement, suppress warning/error
Expand Down
35 changes: 34 additions & 1 deletion Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,12 @@ protected void ProcessDecodedNetworkMessage(UaNetworkMessage networkMessage, str

lock (Lock)
{
// check if reader's MetaData shall be updated
// check if reader's MetaData shall be updated. The metadata message is
// not authenticated: only accept it from the publisher the reader
// receives data from (6.2.8.1 PublisherId filter, null accepts any).
if (reader.DataSetWriterId != 0 &&
reader.DataSetWriterId == networkMessage.DataSetWriterId &&
MatchesPublisherId(reader, networkMessage) &&
(
reader.DataSetMetaData == null ||
!Utils.IsEqual(
Expand Down Expand Up @@ -416,6 +419,36 @@ protected void ProcessDecodedNetworkMessage(UaNetworkMessage networkMessage, str
}
}

/// <summary>
/// Checks the PublisherId of a received network message against the PublisherId
/// filter of a reader, the same way the data messages are filtered when decoded.
/// A reader without PublisherId accepts any publisher.
/// </summary>
private static bool MatchesPublisherId(
DataSetReaderDataType reader,
UaNetworkMessage networkMessage)
{
object readerPublisherId = reader.PublisherId.Value;
if (readerPublisherId == null)
{
return true;
}

switch (networkMessage)
{
case Encoding.JsonNetworkMessage jsonNetworkMessage:
return jsonNetworkMessage.PublisherId != null &&
Utils.IsEqual(
jsonNetworkMessage.PublisherId,
readerPublisherId.ToString());
case Encoding.UadpNetworkMessage uadpNetworkMessage:
return uadpNetworkMessage.PublisherId != null &&
Utils.IsEqual(uadpNetworkMessage.PublisherId, readerPublisherId);
default:
return false;
}
}

/// <summary>
/// Get all dataset readers defined for this UaSubscriber component
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,15 @@ public void CreateServerConfiguration(
ApplicationConfiguration configuration)
{
// setup server configuration node
if (m_serverConfigurationNode.ApplicationUri != null)
{
m_serverConfigurationNode.ApplicationUri.Value = configuration.ApplicationUri;
}
if (m_serverConfigurationNode.ProductUri != null)
{
m_serverConfigurationNode.ProductUri.Value = configuration.ProductUri;
}

m_serverConfigurationNode.ServerCapabilities.Value =
[
.. configuration.ServerConfiguration.ServerCapabilities
Expand Down
34 changes: 34 additions & 0 deletions Libraries/Opc.Ua.Server/Server/StandardServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class StandardServer : SessionServerBase, IStandardServer
/// resources.</param>
protected override void Dispose(bool disposing)
{
Volatile.Write(ref m_reassemblySessionManager, null);
if (disposing)
{
if (m_conformanceUnitsManager != null)
Expand Down Expand Up @@ -2920,6 +2921,11 @@ protected override void OnServerStarting(ApplicationConfiguration configuration)
{
base.OnServerStarting(configuration);

// Listeners open before the session manager exists. Keep their callback
// stable and resolve the current manager at each lookup, including restarts.
Volatile.Write(ref m_reassemblySessionManager, null);
HasActivatedSession = ChannelHasActivatedSession;

// save minimum nonce length.
m_minNonceLength = configuration.SecurityConfiguration.NonceLength;

Expand Down Expand Up @@ -3158,6 +3164,7 @@ await subscriptionManager.StartupAsync(cancellationToken)

// add the session manager to the datastore.
m_serverInternal.SetSessionManager(sessionManager, subscriptionManager);
Volatile.Write(ref m_reassemblySessionManager, sessionManager);

ServerError = null;

Expand Down Expand Up @@ -3242,6 +3249,7 @@ await subscriptionManager.StartupAsync(cancellationToken)
}
catch (Exception e)
{
Volatile.Write(ref m_reassemblySessionManager, null);
const string message = "Unexpected error starting application";
m_logger.LogCritical(Utils.TraceMasks.StartStop, e, message);
Utils.SilentDispose(m_serverInternal);
Expand Down Expand Up @@ -3278,6 +3286,7 @@ await subscriptionManager.StartupAsync(cancellationToken)
/// </summary>
protected override async ValueTask OnServerStoppingAsync(CancellationToken cancellationToken = default)
{
Volatile.Write(ref m_reassemblySessionManager, null);
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - Stopping.");

ShutDownDelay();
Expand Down Expand Up @@ -3862,11 +3871,36 @@ private void SessionChannelKeepAliveEvent(ISession session, SessionEventReason r
}
}

private bool ChannelHasActivatedSession(string channelId)
{
ISessionManager manager = Volatile.Read(ref m_reassemblySessionManager);
if (manager == null || string.IsNullOrEmpty(channelId))
{
return false;
}
if (manager.GetType() == typeof(SessionManager))
{
return ((SessionManager)manager).HasActivatedSession(channelId);
}

// Custom managers (including overrides of SessionManager's lifecycle)
// keep their existing contract and do not need to maintain our snapshot.
foreach (ISession session in manager.GetSessions())
{
if (session.Activated && string.Equals(session.SecureChannelId, channelId, StringComparison.Ordinal))
{
return true;
}
}
return false;
}

private OperationLimitsState OperationLimits
=> ServerInternal.ServerObject.ServerCapabilities.OperationLimits;

private readonly Lock m_registrationLock = new();
private readonly SemaphoreSlim m_semaphoreSlim = new(1, 1);
private ISessionManager m_reassemblySessionManager;
private IServerInternal m_serverInternal;
private ConfigurationWatcher m_configurationWatcher;
private ConfiguredEndpointCollection m_registrationEndpoints;
Expand Down
36 changes: 36 additions & 0 deletions Libraries/Opc.Ua.Server/Session/SessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ protected virtual void Dispose(bool disposing)
// create snapshot of all sessions
KeyValuePair<NodeId, ISession>[] sessions = [.. m_sessions];
m_sessions.Clear();
RefreshActivatedChannels();

foreach (KeyValuePair<NodeId, ISession> sessionKeyValue in sessions)
{
Expand Down Expand Up @@ -136,6 +137,7 @@ public virtual void Shutdown()
// dispose of session objects using a snapshot.
KeyValuePair<NodeId, ISession>[] sessions = [.. m_sessions];
m_sessions.Clear();
RefreshActivatedChannels();

foreach (KeyValuePair<NodeId, ISession> sessionKeyValue in sessions)
{
Expand Down Expand Up @@ -403,6 +405,9 @@ public virtual async ValueTask<CreateSessionResult> CreateSessionAsync(
localeIds,
serverNonceObject);

// Channel transfers need a refresh even when the identity context is unchanged.
RefreshActivatedChannels();

// raise session related event.
if (contextChanged)
{
Expand Down Expand Up @@ -440,6 +445,8 @@ public virtual void CloseSession(NodeId sessionId)
// close the session if removed.
if (session != null)
{
RefreshActivatedChannels();

// raise session related event.
RaiseSessionEvent(session, SessionEventReason.Closing);

Expand Down Expand Up @@ -679,9 +686,38 @@ await m_server.CloseSessionAsync(null, session.Id, false)
}
}

internal bool HasActivatedSession(string channelId)
{
return channelId != null && Volatile.Read(ref m_activatedChannels).Contains(channelId);
}

private void RefreshActivatedChannels()
{
// Serialize the scan as well as publication so a delayed refresh cannot
// overwrite newer membership. Always scan the live registry, not event arguments.
lock (m_activatedChannelsLock)
{
var channels = new HashSet<string>(StringComparer.Ordinal);
foreach (ISession session in m_sessions.Values)
{
string channelId = session.SecureChannelId;
if (session.Activated && !string.IsNullOrEmpty(channelId))
{
channels.Add(channelId);
}
}
Volatile.Write(ref m_activatedChannels, channels);
}
}

private readonly SemaphoreSlim m_semaphoreSlim = new(1, 1);
private readonly IServerInternal m_server;
private readonly ILogger m_logger;

// Published sets are never mutated. Readers do not take the refresh lock.
private readonly object m_activatedChannelsLock = new();
private HashSet<string> m_activatedChannels = new(StringComparer.Ordinal);

private readonly NodeIdDictionary<ISession> m_sessions;
private uint m_lastSessionId;
private readonly ManualResetEvent m_shutdownEvent;
Expand Down
11 changes: 11 additions & 0 deletions Libraries/Opc.Ua.Server/Subscription/Subscription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,17 @@ await m_server.NodeManager
m_logger.LogTrace("Failed to transfer {Count} Monitored Items", badTransfers);
}

lock (m_lock)
{
ResetLifetimeCount();

// An abandoned subscription can be waiting for a keep-alive without having
// checked its monitored item queues. Force the next timer check to inspect
// queued data before the new session's first Publish, even when initial
// values were not requested.
m_publishTimerExpiry = HiResClock.TickCount64 - 1;
}

lock (DiagnosticsWriteLock)
{
Diagnostics.SessionId = Session.Id;
Expand Down
4 changes: 4 additions & 0 deletions Stack/Opc.Ua.Core/Opc.Ua.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Opc.Ua.Core.Tests" />
<InternalsVisibleTo Include="Opc.Ua.Server" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<PackageId>$(PackageId).Debug</PackageId>
Expand All @@ -37,6 +38,9 @@
<ProjectReference Include="..\..\Libraries\Opc.Ua.Security.Certificates\Opc.Ua.Security.Certificates.csproj" />
<ProjectReference Include="..\Opc.Ua.Types\Opc.Ua.Types.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Opc.Ua.Types\Encoders\XmlElementDepth.cs" Link="Types\Encoders\XmlElementDepth.cs" />
</ItemGroup>
<Target Name="GetPackagingOutputs" />
<!-- Produces a zipped version of the Nodeset 2 to reduce assembly size! -->
<!-- This is not used by the stack, but was added as convinience for applications to get the nodesets -->
Expand Down
Loading
Loading