diff --git a/docs/DependencyInjection.md b/docs/DependencyInjection.md
index e06e6ab4a7..af5225bd82 100644
--- a/docs/DependencyInjection.md
+++ b/docs/DependencyInjection.md
@@ -936,7 +936,6 @@ unchanged. The data binds directly from
{
"EndpointUrl": "opc.tcp://client.example.com:4841",
"Timeout": 30000,
- "MaxSessionCount": 1,
"Enabled": true
}
]
diff --git a/docs/ReverseConnect.md b/docs/ReverseConnect.md
index 570a08fd09..e7b1e875b0 100644
--- a/docs/ReverseConnect.md
+++ b/docs/ReverseConnect.md
@@ -220,7 +220,6 @@ services.AddOpcUa()
{
EndpointUrl = "opc.tcp://client.example.com:65300",
Timeout = 30000,
- MaxSessionCount = 1,
Enabled = true
});
});
diff --git a/samples/Reference/ConsoleReferenceServer/Ctt.ReferenceServer.Config.xml b/samples/Reference/ConsoleReferenceServer/Ctt.ReferenceServer.Config.xml
index c42ce2a024..6ef730a465 100644
--- a/samples/Reference/ConsoleReferenceServer/Ctt.ReferenceServer.Config.xml
+++ b/samples/Reference/ConsoleReferenceServer/Ctt.ReferenceServer.Config.xml
@@ -228,7 +228,6 @@
opc.tcp://localhost:65300
- 0
true
diff --git a/samples/Reference/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml b/samples/Reference/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml
index 6fac3c289e..e9e81756b5 100644
--- a/samples/Reference/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml
+++ b/samples/Reference/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml
@@ -371,7 +371,6 @@
opc.tcp://localhost:65300
- 0
true
diff --git a/src/Opc.Ua.Core/Schema/ApplicationConfiguration.cs b/src/Opc.Ua.Core/Schema/ApplicationConfiguration.cs
index 58fc7ae7d7..daeea64b45 100644
--- a/src/Opc.Ua.Core/Schema/ApplicationConfiguration.cs
+++ b/src/Opc.Ua.Core/Schema/ApplicationConfiguration.cs
@@ -1623,18 +1623,15 @@ namespace Opc.Ua
///
/// The maximum count of active reverse connect sessions.
- /// 0 or undefined means unlimited number of sessions.
- /// 1 means a single connection is created at a time.
- /// n disables reverse hello once the total number of sessions
- /// in the server reaches n.
+ /// This setting is ignored and is no longer serialized.
///
- [DataTypeField(Order = 2)]
+ [Obsolete("MaxSessionCount is no longer supported and is ignored.")]
public int MaxSessionCount { get; set; }
///
/// Specifies whether the sending of reverse connect attempts is enabled.
///
- [DataTypeField(Order = 3, DefaultValueHandling = DefaultValueHandling.Emit)]
+ [DataTypeField(Order = 2, DefaultValueHandling = DefaultValueHandling.Emit)]
public bool Enabled { get; set; } = true;
}
diff --git a/src/Opc.Ua.Core/Schema/ApplicationConfiguration.xsd b/src/Opc.Ua.Core/Schema/ApplicationConfiguration.xsd
index fcf424e534..fa338eeece 100644
--- a/src/Opc.Ua.Core/Schema/ApplicationConfiguration.xsd
+++ b/src/Opc.Ua.Core/Schema/ApplicationConfiguration.xsd
@@ -206,7 +206,6 @@
-
diff --git a/src/Opc.Ua.Gds.Server/Hosting/GdsServerHostedService.cs b/src/Opc.Ua.Gds.Server/Hosting/GdsServerHostedService.cs
index 6ab0273d36..88d8cf29b8 100644
--- a/src/Opc.Ua.Gds.Server/Hosting/GdsServerHostedService.cs
+++ b/src/Opc.Ua.Gds.Server/Hosting/GdsServerHostedService.cs
@@ -638,7 +638,6 @@ namespace Opc.Ua.Gds.Server.Hosting
{
EndpointUrl = c.EndpointUrl,
Timeout = c.Timeout,
- MaxSessionCount = c.MaxSessionCount,
Enabled = c.Enabled
};
}
diff --git a/src/Opc.Ua.Server/Hosting/OpcUaServerApplicationConfigurationFeature.cs b/src/Opc.Ua.Server/Hosting/OpcUaServerApplicationConfigurationFeature.cs
index bb7bec1b66..561374f685 100644
--- a/src/Opc.Ua.Server/Hosting/OpcUaServerApplicationConfigurationFeature.cs
+++ b/src/Opc.Ua.Server/Hosting/OpcUaServerApplicationConfigurationFeature.cs
@@ -141,7 +141,6 @@ namespace Opc.Ua.Server.Hosting
{
EndpointUrl = client.EndpointUrl,
Timeout = client.Timeout,
- MaxSessionCount = client.MaxSessionCount,
Enabled = client.Enabled
};
}
diff --git a/src/Opc.Ua.Server/Hosting/OpcUaServerBuilderExtensions.cs b/src/Opc.Ua.Server/Hosting/OpcUaServerBuilderExtensions.cs
index 220fdafb26..bf4607a9b4 100644
--- a/src/Opc.Ua.Server/Hosting/OpcUaServerBuilderExtensions.cs
+++ b/src/Opc.Ua.Server/Hosting/OpcUaServerBuilderExtensions.cs
@@ -1928,8 +1928,6 @@ namespace Microsoft.Extensions.DependencyInjection
BindString(child, nameof(ServerReverseConnectClientOptions.EndpointUrl),
value => client.EndpointUrl = value);
BindInt32(child, nameof(ServerReverseConnectClientOptions.Timeout), value => client.Timeout = value);
- BindInt32(child, nameof(ServerReverseConnectClientOptions.MaxSessionCount),
- value => client.MaxSessionCount = value);
BindBoolean(child, nameof(ServerReverseConnectClientOptions.Enabled), value => client.Enabled = value);
reverseConnect.Clients.Add(client);
}
diff --git a/src/Opc.Ua.Server/Hosting/ServerReverseConnectOptions.cs b/src/Opc.Ua.Server/Hosting/ServerReverseConnectOptions.cs
index a5981c1b64..14327d7945 100644
--- a/src/Opc.Ua.Server/Hosting/ServerReverseConnectOptions.cs
+++ b/src/Opc.Ua.Server/Hosting/ServerReverseConnectOptions.cs
@@ -94,9 +94,9 @@ namespace Opc.Ua.Server.Hosting
///
/// Maximum simultaneous reverse-connect sessions for this client.
- /// Zero means unlimited; 1 permits a single connection at a
- /// time.
+ /// This setting is ignored.
///
+ [System.Obsolete("MaxSessionCount is no longer supported and is ignored.")]
public int MaxSessionCount { get; set; }
///
diff --git a/src/Opc.Ua.Server/Server/ReverseConnectServer.cs b/src/Opc.Ua.Server/Server/ReverseConnectServer.cs
index 2b4f6659f2..1643cbd916 100644
--- a/src/Opc.Ua.Server/Server/ReverseConnectServer.cs
+++ b/src/Opc.Ua.Server/Server/ReverseConnectServer.cs
@@ -74,6 +74,25 @@ namespace Opc.Ua.Server
///
public class ReverseConnectProperty
{
+ ///
+ /// Initialize a reverse connect server property.
+ ///
+ /// The Url of the reverse connect client.
+ /// The timeout to use for a reverse connect attempt.
+ /// If this is an application configuration entry.
+ /// If the connection is enabled.
+ public ReverseConnectProperty(
+ Uri clientUrl,
+ int timeout,
+ bool configEntry,
+ bool enabled = true)
+ {
+ ClientUrl = clientUrl;
+ Timeout = timeout > 0 ? timeout : ReverseConnectServer.DefaultReverseConnectTimeout;
+ ConfigEntry = configEntry;
+ Enabled = enabled;
+ }
+
///
/// Initialize a reverse connect server property.
///
@@ -82,18 +101,18 @@ namespace Opc.Ua.Server
/// The maximum number of sessions allowed to the client.
/// If this is an application configuration entry.
/// If the connection is enabled.
+ [Obsolete("MaxSessionCount is no longer supported and has been removed from configuration.")]
public ReverseConnectProperty(
Uri clientUrl,
int timeout,
int maxSessionCount,
bool configEntry,
bool enabled = true)
+ : this(clientUrl, timeout, configEntry, enabled)
{
- ClientUrl = clientUrl;
- Timeout = timeout > 0 ? timeout : ReverseConnectServer.DefaultReverseConnectTimeout;
+#pragma warning disable CS0618
MaxSessionCount = maxSessionCount;
- ConfigEntry = configEntry;
- Enabled = enabled;
+#pragma warning restore CS0618
}
///
@@ -124,6 +143,7 @@ namespace Opc.Ua.Server
///
/// The maximum number of sessions allowed to the client.
///
+ [Obsolete("MaxSessionCount is no longer supported and has been removed from configuration.")]
public int MaxSessionCount;
///
@@ -232,12 +252,32 @@ namespace Opc.Ua.Server
/// Add a reverse connection url.
///
///
+ [System.Runtime.CompilerServices.OverloadResolutionPriority(1)]
+ public virtual void AddReverseConnection(
+ Uri url,
+ int timeout = 0,
+ bool enabled = true)
+ {
+#pragma warning disable CS0618
+ AddReverseConnection(url, timeout, maxSessionCount: 0, enabled);
+#pragma warning restore CS0618
+ }
+
+ ///
+ /// Add a reverse connection url.
+ ///
+ /// The Url of the reverse connect client.
+ /// The timeout to use for a reverse connect attempt.
+ /// The maximum number of sessions allowed (deprecated and ignored).
+ /// If the connection is enabled.
+ [Obsolete("The maxSessionCount parameter is no longer supported and will be ignored.")]
public virtual void AddReverseConnection(
Uri url,
int timeout = 0,
int maxSessionCount = 0,
bool enabled = true)
{
+ _ = maxSessionCount;
if (m_connections.ContainsKey(url))
{
throw new ArgumentException(
@@ -247,7 +287,6 @@ namespace Opc.Ua.Server
var reverseConnection = new ReverseConnectProperty(
url,
timeout,
- maxSessionCount,
false,
enabled);
@@ -329,15 +368,8 @@ namespace Opc.Ua.Server
// try the reverse connect
if (reverseConnection.Enabled &&
- (
- reverseConnection.MaxSessionCount == 0 ||
- (
- reverseConnection.MaxSessionCount == 1 &&
- reverseConnection.LastState == ReverseConnectState.Closed
- ) ||
- reverseConnection.MaxSessionCount > ServerInternal.SessionManager
- .GetSessions()
- .Count))
+ reverseConnection.LastState != ReverseConnectState.Connecting &&
+ reverseConnection.LastState != ReverseConnectState.Rejected)
{
try
{
@@ -526,7 +558,6 @@ namespace Opc.Ua.Server
{
if (existing.ConfigEntry)
{
- existing.MaxSessionCount = client.MaxSessionCount;
existing.Enabled = client.Enabled;
}
else
@@ -539,7 +570,6 @@ namespace Opc.Ua.Server
m_connections[uri] = new ReverseConnectProperty(
uri,
client.Timeout,
- client.MaxSessionCount,
true,
client.Enabled);
m_logger.ReverseConnectionAddedForEndpointUrlUri(uri);
diff --git a/tests/Opc.Ua.Aot.Tests/Quickstarts.ReferenceServer.Config.xml b/tests/Opc.Ua.Aot.Tests/Quickstarts.ReferenceServer.Config.xml
index a62cc996e6..2390b0d143 100644
--- a/tests/Opc.Ua.Aot.Tests/Quickstarts.ReferenceServer.Config.xml
+++ b/tests/Opc.Ua.Aot.Tests/Quickstarts.ReferenceServer.Config.xml
@@ -289,7 +289,6 @@
opc.tcp://localhost:65300
- 0
true
diff --git a/tests/Opc.Ua.Configuration.Tests/ConfigurationRoundTripTests.cs b/tests/Opc.Ua.Configuration.Tests/ConfigurationRoundTripTests.cs
index 840015bef2..f84c86433d 100644
--- a/tests/Opc.Ua.Configuration.Tests/ConfigurationRoundTripTests.cs
+++ b/tests/Opc.Ua.Configuration.Tests/ConfigurationRoundTripTests.cs
@@ -872,13 +872,11 @@ namespace Opc.Ua.Configuration.Tests
rc.Clients[0].EndpointUrl,
Is.EqualTo("opc.tcp://client1:65300"));
Assert.That(rc.Clients[0].Timeout, Is.EqualTo(5000));
- Assert.That(rc.Clients[0].MaxSessionCount, Is.EqualTo(10));
Assert.That(rc.Clients[0].Enabled, Is.True);
Assert.That(
rc.Clients[1].EndpointUrl,
Is.EqualTo("opc.tcp://client2:65301"));
Assert.That(rc.Clients[1].Timeout, Is.EqualTo(10000));
- Assert.That(rc.Clients[1].MaxSessionCount, Is.EqualTo(5));
Assert.That(rc.Clients[1].Enabled, Is.False);
Assert.That(rc.ConnectInterval, Is.EqualTo(20000));
Assert.That(rc.ConnectTimeout, Is.EqualTo(45000));
diff --git a/tests/Opc.Ua.Configuration.Tests/test-server-full.xml b/tests/Opc.Ua.Configuration.Tests/test-server-full.xml
index b24be686bd..4af8e0a2b9 100644
--- a/tests/Opc.Ua.Configuration.Tests/test-server-full.xml
+++ b/tests/Opc.Ua.Configuration.Tests/test-server-full.xml
@@ -107,13 +107,11 @@
opc.tcp://client1:65300
5000
- 10
true
opc.tcp://client2:65301
10000
- 5
false
diff --git a/tests/Opc.Ua.Lens.Tests/Samples/RepositorySampleLiveTests.Identities.cs b/tests/Opc.Ua.Lens.Tests/Samples/RepositorySampleLiveTests.Identities.cs
index 1078142587..47b990ca21 100644
--- a/tests/Opc.Ua.Lens.Tests/Samples/RepositorySampleLiveTests.Identities.cs
+++ b/tests/Opc.Ua.Lens.Tests/Samples/RepositorySampleLiveTests.Identities.cs
@@ -120,8 +120,7 @@ namespace UaLens.Tests.Samples
options.ReverseConnect.Clients.Add(new ServerReverseConnectClientOptions
{
EndpointUrl = listenerUrl,
- Timeout = 10000,
- MaxSessionCount = 1
+ Timeout = 10000
});
}
}).AddCertificateManager(manager);
diff --git a/tests/Opc.Ua.Server.Tests/Hosting/HostedReverseConnectIntegrationTests.cs b/tests/Opc.Ua.Server.Tests/Hosting/HostedReverseConnectIntegrationTests.cs
index 50e1cf4487..c143aebef7 100644
--- a/tests/Opc.Ua.Server.Tests/Hosting/HostedReverseConnectIntegrationTests.cs
+++ b/tests/Opc.Ua.Server.Tests/Hosting/HostedReverseConnectIntegrationTests.cs
@@ -106,8 +106,7 @@ namespace Opc.Ua.Server.Tests.Hosting
options.ReverseConnect.Clients.Add(new ServerReverseConnectClientOptions
{
EndpointUrl = clientUrl.AbsoluteUri,
- Timeout = 10000,
- MaxSessionCount = 1
+ Timeout = 10000
});
});
}
@@ -237,7 +236,6 @@ namespace Opc.Ua.Server.Tests.Hosting
{
EndpointUrl = clientUrl.AbsoluteUri,
Timeout = 10000,
- MaxSessionCount = 1,
Enabled = true
}
]
diff --git a/tests/Opc.Ua.Server.Tests/Hosting/OpcUaServerApplicationConfigurationFeatureTests.cs b/tests/Opc.Ua.Server.Tests/Hosting/OpcUaServerApplicationConfigurationFeatureTests.cs
index 7f181f37f3..68fa118345 100644
--- a/tests/Opc.Ua.Server.Tests/Hosting/OpcUaServerApplicationConfigurationFeatureTests.cs
+++ b/tests/Opc.Ua.Server.Tests/Hosting/OpcUaServerApplicationConfigurationFeatureTests.cs
@@ -178,7 +178,6 @@ namespace Opc.Ua.Server.Tests.Hosting
{
EndpointUrl = "opc.tcp://reverse-client:4841",
Timeout = 500,
- MaxSessionCount = 2,
Enabled = true
});
options.EndpointUrls.Add("opc.tcp://localhost:0/ReverseConnect");
@@ -195,7 +194,6 @@ namespace Opc.Ua.Server.Tests.Hosting
Assert.That(reverseConnect.Clients.Count, Is.EqualTo(1));
Assert.That(reverseConnect.Clients[0].EndpointUrl, Is.EqualTo("opc.tcp://reverse-client:4841"));
Assert.That(reverseConnect.Clients[0].Timeout, Is.EqualTo(500));
- Assert.That(reverseConnect.Clients[0].MaxSessionCount, Is.EqualTo(2));
Assert.That(reverseConnect.Clients[0].Enabled, Is.True);
}
diff --git a/tests/Opc.Ua.Server.Tests/Hosting/OpcUaServerOptionsCoverageTests.cs b/tests/Opc.Ua.Server.Tests/Hosting/OpcUaServerOptionsCoverageTests.cs
index c854a048a9..880aafb1b4 100644
--- a/tests/Opc.Ua.Server.Tests/Hosting/OpcUaServerOptionsCoverageTests.cs
+++ b/tests/Opc.Ua.Server.Tests/Hosting/OpcUaServerOptionsCoverageTests.cs
@@ -89,7 +89,6 @@ namespace Opc.Ua.Server.Tests.Hosting
{
EndpointUrl = "opc.tcp://client.local:4841",
Timeout = 7000,
- MaxSessionCount = 2,
Enabled = true
});
});
@@ -104,7 +103,6 @@ namespace Opc.Ua.Server.Tests.Hosting
ServerReverseConnectClientOptions client = opts.ReverseConnect.Clients[0];
Assert.That(client.EndpointUrl, Is.EqualTo("opc.tcp://client.local:4841"));
Assert.That(client.Timeout, Is.EqualTo(7000));
- Assert.That(client.MaxSessionCount, Is.EqualTo(2));
Assert.That(client.Enabled, Is.True);
}
diff --git a/tests/Opc.Ua.Server.Tests/Hosting/ServerCompositionHostingTests.cs b/tests/Opc.Ua.Server.Tests/Hosting/ServerCompositionHostingTests.cs
index e49c6d9d3e..7d423a4b0e 100644
--- a/tests/Opc.Ua.Server.Tests/Hosting/ServerCompositionHostingTests.cs
+++ b/tests/Opc.Ua.Server.Tests/Hosting/ServerCompositionHostingTests.cs
@@ -1424,8 +1424,7 @@ namespace Opc.Ua.Server.Tests.Hosting
options.ReverseConnect.Clients.Add(new ServerReverseConnectClientOptions
{
EndpointUrl = enabled.ToString(),
- Timeout = 3210,
- MaxSessionCount = 0
+ Timeout = 3210
});
options.ReverseConnect.Clients.Add(new ServerReverseConnectClientOptions
{
@@ -2132,8 +2131,7 @@ namespace Opc.Ua.Server.Tests.Hosting
options.ReverseConnect.Clients.Add(new ServerReverseConnectClientOptions
{
EndpointUrl = client.ToString(),
- Timeout = 1456,
- MaxSessionCount = 0
+ Timeout = 1456
});
});
await using ConfiguredAsyncDisposable cleanup = fixture.ConfigureAwait(false);
@@ -2236,13 +2234,11 @@ namespace Opc.Ua.Server.Tests.Hosting
opc.tcp://localhost:0/xml-enabled
1234
- 0
true
opc.tcp://localhost:0/xml-disabled
4321
- 0
false
diff --git a/tests/Opc.Ua.Server.Tests/ReverseConnectReloadRegressionTests.cs b/tests/Opc.Ua.Server.Tests/ReverseConnectReloadRegressionTests.cs
index db60bb7944..d3323a9735 100644
--- a/tests/Opc.Ua.Server.Tests/ReverseConnectReloadRegressionTests.cs
+++ b/tests/Opc.Ua.Server.Tests/ReverseConnectReloadRegressionTests.cs
@@ -57,7 +57,7 @@ namespace Opc.Ua.Server.Tests
CreateConfiguration("configured").ServerConfiguration!.ReverseConnect;
ReloadableReverseConnectServer server = await fixture.StartAsync().ConfigureAwait(false);
var dynamicUrl = new Uri("opc.tcp://localhost:4840/dynamic");
- server.AddReverseConnection(dynamicUrl, timeout: 1234, maxSessionCount: 2, enabled: false);
+ server.AddReverseConnection(dynamicUrl, timeout: 1234, enabled: false);
ReverseConnectProperty retained = server.GetReverseConnections()[dynamicUrl];
Assert.That(server.GetReverseConnections(), Has.Count.EqualTo(2));
@@ -70,7 +70,6 @@ namespace Opc.Ua.Server.Tests
Assert.That(server.GetReverseConnections()[dynamicUrl], Is.SameAs(retained));
Assert.That(retained.ConfigEntry, Is.False);
Assert.That(retained.Timeout, Is.EqualTo(1234));
- Assert.That(retained.MaxSessionCount, Is.EqualTo(2));
Assert.That(retained.Enabled, Is.False);
}
finally
@@ -88,7 +87,7 @@ namespace Opc.Ua.Server.Tests
{
ReloadableReverseConnectServer server = await fixture.StartAsync().ConfigureAwait(false);
var dynamicUrl = new Uri("opc.tcp://localhost:4840/dynamic");
- server.AddReverseConnection(dynamicUrl, timeout: 1234, maxSessionCount: 2, enabled: false);
+ server.AddReverseConnection(dynamicUrl, timeout: 1234, enabled: false);
ReverseConnectProperty retained = server.GetReverseConnections()[dynamicUrl];
fixture.Config.ServerConfiguration!.ReverseConnect = new ReverseConnectServerConfiguration
{
@@ -98,7 +97,6 @@ namespace Opc.Ua.Server.Tests
{
EndpointUrl = dynamicUrl.AbsoluteUri,
Timeout = 9000,
- MaxSessionCount = 8,
Enabled = true
}
]
@@ -111,7 +109,6 @@ namespace Opc.Ua.Server.Tests
Assert.That(server.GetReverseConnections()[dynamicUrl], Is.SameAs(retained));
Assert.That(retained.ConfigEntry, Is.False);
Assert.That(retained.Timeout, Is.EqualTo(1234));
- Assert.That(retained.MaxSessionCount, Is.EqualTo(2));
Assert.That(retained.Enabled, Is.False);
}
finally
@@ -128,7 +125,7 @@ namespace Opc.Ua.Server.Tests
{
using var server = new ReverseConnectServer(NUnitTelemetryContext.Create(), new FakeTimeProvider());
var dynamicUrl = new Uri("opc.tcp://localhost:4840/dynamic");
- server.AddReverseConnection(dynamicUrl, timeout: 1234, maxSessionCount: 2, enabled: false);
+ server.AddReverseConnection(dynamicUrl, timeout: 1234, enabled: false);
MethodInfo method = typeof(ReverseConnectServer).GetMethod(
"UpdateConfiguration", BindingFlags.Instance | BindingFlags.NonPublic)!;
#if NET5_0_OR_GREATER
@@ -148,7 +145,6 @@ namespace Opc.Ua.Server.Tests
ReverseConnectProperty retained = server.GetReverseConnections()[dynamicUrl];
Assert.That(retained.ConfigEntry, Is.False);
Assert.That(retained.Timeout, Is.EqualTo(1234));
- Assert.That(retained.MaxSessionCount, Is.EqualTo(2));
Assert.That(retained.Enabled, Is.False);
}
diff --git a/tests/Opc.Ua.Server.Tests/ReverseConnectServerCoverageTests.cs b/tests/Opc.Ua.Server.Tests/ReverseConnectServerCoverageTests.cs
index b68da84d82..610cf27a41 100644
--- a/tests/Opc.Ua.Server.Tests/ReverseConnectServerCoverageTests.cs
+++ b/tests/Opc.Ua.Server.Tests/ReverseConnectServerCoverageTests.cs
@@ -53,7 +53,6 @@ namespace Opc.Ua.Server.Tests
var property = new ReverseConnectProperty(
s_clientUrl,
timeout: 5000,
- maxSessionCount: 3,
configEntry: true,
enabled: true);
@@ -61,7 +60,6 @@ namespace Opc.Ua.Server.Tests
{
Assert.That(property.ClientUrl, Is.EqualTo(s_clientUrl));
Assert.That(property.Timeout, Is.EqualTo(5000));
- Assert.That(property.MaxSessionCount, Is.EqualTo(3));
Assert.That(property.ConfigEntry, Is.True);
Assert.That(property.Enabled, Is.True);
Assert.That(property.LastState, Is.EqualTo(ReverseConnectState.Closed));
@@ -76,7 +74,6 @@ namespace Opc.Ua.Server.Tests
var property = new ReverseConnectProperty(
s_clientUrl,
timeout,
- maxSessionCount: 0,
configEntry: false);
Assert.That(
@@ -90,7 +87,6 @@ namespace Opc.Ua.Server.Tests
var property = new ReverseConnectProperty(
s_clientUrl,
timeout: 1000,
- maxSessionCount: 0,
configEntry: false);
Assert.That(property.Enabled, Is.True);
@@ -102,7 +98,6 @@ namespace Opc.Ua.Server.Tests
var property = new ReverseConnectProperty(
s_clientUrl,
timeout: 1000,
- maxSessionCount: 0,
configEntry: false,
enabled: false);
@@ -115,12 +110,10 @@ namespace Opc.Ua.Server.Tests
var property = new ReverseConnectProperty(
s_clientUrl,
timeout: 1000,
- maxSessionCount: 0,
configEntry: false);
var rejectTime = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
property.LastState = ReverseConnectState.Rejected;
- property.MaxSessionCount = 7;
property.Enabled = false;
property.RejectTime = rejectTime;
property.ServiceResult = new ServiceResult(StatusCodes.BadTimeout);
@@ -128,7 +121,6 @@ namespace Opc.Ua.Server.Tests
Assert.Multiple(() =>
{
Assert.That(property.LastState, Is.EqualTo(ReverseConnectState.Rejected));
- Assert.That(property.MaxSessionCount, Is.EqualTo(7));
Assert.That(property.Enabled, Is.False);
Assert.That(property.RejectTime, Is.EqualTo(rejectTime));
Assert.That(property.ServiceResult, Is.Not.Null);
@@ -158,7 +150,6 @@ namespace Opc.Ua.Server.Tests
Assert.Multiple(() =>
{
Assert.That(property.Timeout, Is.EqualTo(ReverseConnectServer.DefaultReverseConnectTimeout));
- Assert.That(property.MaxSessionCount, Is.Zero);
Assert.That(property.Enabled, Is.True);
Assert.That(property.ConfigEntry, Is.False);
});
@@ -172,14 +163,12 @@ namespace Opc.Ua.Server.Tests
server.AddReverseConnection(
s_clientUrl,
timeout: 2500,
- maxSessionCount: 4,
enabled: false);
ReverseConnectProperty property = server.GetReverseConnections()[s_clientUrl];
Assert.Multiple(() =>
{
Assert.That(property.Timeout, Is.EqualTo(2500));
- Assert.That(property.MaxSessionCount, Is.EqualTo(4));
Assert.That(property.Enabled, Is.False);
});
}
diff --git a/tests/Opc.Ua.Server.Tests/ReverseConnectServerSessionAccountingTests.cs b/tests/Opc.Ua.Server.Tests/ReverseConnectServerSessionAccountingTests.cs
new file mode 100644
index 0000000000..ab8e197e81
--- /dev/null
+++ b/tests/Opc.Ua.Server.Tests/ReverseConnectServerSessionAccountingTests.cs
@@ -0,0 +1,326 @@
+/* ========================================================================
+ * 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;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Time.Testing;
+using NUnit.Framework;
+using Opc.Ua.Server.TestFramework;
+using Opc.Ua.Tests;
+
+namespace Opc.Ua.Server.Tests
+{
+ ///
+ /// Regression tests for Issue #4863: reverse connect connection admission must not
+ /// be blocked by unrelated server sessions or an existing connection.
+ ///
+ [TestFixture]
+ [Category("ReverseConnect")]
+ [NonParallelizable]
+ public class ReverseConnectServerSessionAccountingTests
+ {
+ private static readonly Uri s_reverseClientUri = new("opc.tcp://reverseclient.local:4840");
+
+ private sealed class TestableReverseConnectServer : ReverseConnectServer
+ {
+ public TestableReverseConnectServer(ITelemetryContext telemetry)
+ : this(telemetry, new FakeTimeProvider())
+ {
+ }
+
+ public TestableReverseConnectServer(ITelemetryContext telemetry, TimeProvider timeProvider)
+ : base(telemetry, timeProvider)
+ {
+ }
+
+ ///
+ /// Number of calls dispatched to the legacy overload.
+ ///
+ public int LegacyAddReverseConnectionCallCount { get; private set; }
+
+ [Obsolete]
+ public override void AddReverseConnection(
+ Uri url,
+ int timeout = 0,
+ int maxSessionCount = 0,
+ bool enabled = true)
+ {
+ LegacyAddReverseConnectionCallCount++;
+ base.AddReverseConnection(url, timeout, maxSessionCount, enabled);
+ }
+
+ public void InvokeOnReverseConnect(long version)
+ {
+ MethodInfo method = typeof(ReverseConnectServer).GetMethod(
+ "OnReverseConnect",
+ BindingFlags.Instance | BindingFlags.NonPublic)!;
+ method.Invoke(this, [version]);
+ }
+ }
+
+ ///
+ /// Verifies that the new overload still dispatches through the legacy virtual overload.
+ ///
+ [Test]
+ public void AddReverseConnectionPreservesLegacyVirtualDispatch()
+ {
+ using var server = new TestableReverseConnectServer(NUnitTelemetryContext.Create());
+
+ server.AddReverseConnection(s_reverseClientUri);
+
+ Assert.That(server.LegacyAddReverseConnectionCallCount, Is.EqualTo(1));
+ Assert.That(server.GetReverseConnections(), Contains.Key(s_reverseClientUri));
+ }
+
+ [Test]
+ public void ConnectedReverseConnection_CanBeReconnected()
+ {
+ var fakeTime = new FakeTimeProvider();
+ using var server = new TestableReverseConnectServer(NUnitTelemetryContext.Create(), fakeTime);
+
+ server.AddReverseConnection(s_reverseClientUri, timeout: 5000, enabled: true);
+ ReverseConnectProperty property = server.GetReverseConnections()[s_reverseClientUri];
+
+ // Simulate the reverse connection has successfully established
+ property.LastState = ReverseConnectState.Connected;
+
+ // Trigger the reverse connect tick
+ server.InvokeOnReverseConnect(0);
+
+ // A new connection is required to recreate a session without outbound discovery.
+ Assert.That(
+ property.LastState,
+ Is.EqualTo(ReverseConnectState.Errored),
+ "A Connected reverse connection must allow another connection attempt.");
+ Assert.That(property.ServiceResult?.AdditionalInfo, Does.Contain("No suitable listener found"));
+ }
+
+ ///
+ /// Verifies that an established reverse connection can be replaced when the server has no sessions.
+ ///
+ [Test]
+ public async Task ConnectedReverseConnectionCanBeReplacedWhenStartedServerHasZeroSessionsAsync()
+ {
+ var fakeTime = new FakeTimeProvider();
+ var fixture = new ServerFixture(
+ telemetry => new TestableReverseConnectServer(telemetry, fakeTime));
+ try
+ {
+ await fixture.LoadConfigurationAsync().ConfigureAwait(false);
+ fixture.Config.ServerConfiguration!.ReverseConnect = new ReverseConnectServerConfiguration
+ {
+ Clients = []
+ };
+ TestableReverseConnectServer server = await fixture.StartAsync().ConfigureAwait(false);
+
+ server.AddReverseConnection(s_reverseClientUri, timeout: 5000, enabled: true);
+ ReverseConnectProperty property = server.GetReverseConnections()[s_reverseClientUri];
+
+ // Connection is already established, but server has 0 active sessions
+ property.LastState = ReverseConnectState.Connected;
+
+ // Fire the server's current timer (initial delay is one second)
+ fakeTime.Advance(TimeSpan.FromSeconds(2));
+
+ Assert.That(
+ property.LastState,
+ Is.EqualTo(ReverseConnectState.Connecting),
+ "A Connected reverse connection must allow a replacement connection attempt.");
+ }
+ finally
+ {
+ await fixture.StopAsync().ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Verifies that reverse connections are attempted with a full legacy session threshold.
+ ///
+ [TestCase(ReverseConnectState.Closed)]
+ [TestCase(ReverseConnectState.Errored)]
+ [TestCase(ReverseConnectState.Connected)]
+ public async Task ReverseConnectionIsAttemptedWhenLegacySessionThresholdIsReachedAsync(
+ ReverseConnectState initialState)
+ {
+ var fakeTime = new FakeTimeProvider();
+ var fixture = new ServerFixture(
+ telemetry => new TestableReverseConnectServer(telemetry, fakeTime));
+ try
+ {
+ await fixture.LoadConfigurationAsync().ConfigureAwait(false);
+ fixture.Config.ServerConfiguration!.ReverseConnect = new ReverseConnectServerConfiguration
+ {
+ Clients = []
+ };
+ TestableReverseConnectServer server = await fixture.StartAsync().ConfigureAwait(false);
+ await server.CreateAndActivateSessionAsync("reverse-connect-session-1").ConfigureAwait(false);
+ await server.CreateAndActivateSessionAsync("reverse-connect-session-2").ConfigureAwait(false);
+
+ server.AddReverseConnection(s_reverseClientUri, timeout: 5000, enabled: true);
+ ReverseConnectProperty property = server.GetReverseConnections()[s_reverseClientUri];
+#pragma warning disable CS0618
+ property.MaxSessionCount = 2;
+#pragma warning restore CS0618
+ property.LastState = initialState;
+
+ Assert.That(server.CurrentInstance.SessionManager.GetSessions(), Has.Count.EqualTo(2));
+
+ // Fire the current timer after its one-second initial delay.
+ fakeTime.Advance(TimeSpan.FromSeconds(2));
+
+ Assert.That(
+ property.LastState,
+ Is.EqualTo(ReverseConnectState.Connecting),
+ $"A {initialState} connection must be attempted even when the legacy session threshold is reached.");
+ }
+ finally
+ {
+ await fixture.StopAsync().ConfigureAwait(false);
+ }
+ }
+
+ [Test]
+ public void ConnectingReverseConnection_IsNotReconnected_WhileConnectionInProgress()
+ {
+ var fakeTime = new FakeTimeProvider();
+ using var server = new TestableReverseConnectServer(NUnitTelemetryContext.Create(), fakeTime);
+
+ server.AddReverseConnection(s_reverseClientUri, timeout: 5000, enabled: true);
+ ReverseConnectProperty property = server.GetReverseConnections()[s_reverseClientUri];
+
+ // Simulate connection attempt in flight
+ property.LastState = ReverseConnectState.Connecting;
+
+ server.InvokeOnReverseConnect(0);
+
+ Assert.That(
+ property.LastState,
+ Is.EqualTo(ReverseConnectState.Connecting),
+ "A reverse connection that is currently Connecting must not trigger a redundant connection attempt.");
+ }
+
+ [Test]
+ public void ClosedReverseConnection_InitiatesConnection_RegardlessOfServerSessions()
+ {
+ var fakeTime = new FakeTimeProvider();
+ using var server = new TestableReverseConnectServer(NUnitTelemetryContext.Create(), fakeTime);
+
+ server.AddReverseConnection(s_reverseClientUri, timeout: 5000, enabled: true);
+ ReverseConnectProperty property = server.GetReverseConnections()[s_reverseClientUri];
+
+ Assert.That(property.LastState, Is.EqualTo(ReverseConnectState.Closed));
+
+ // Tick should attempt connection (which throws ArgumentException because no listener is configured in unit test)
+ server.InvokeOnReverseConnect(0);
+
+ Assert.That(
+ property.LastState,
+ Is.EqualTo(ReverseConnectState.Errored),
+ "A Closed connection must attempt connection on tick.");
+ Assert.That(
+ property.ServiceResult?.AdditionalInfo,
+ Does.Contain("No suitable listener found"));
+ }
+
+ [Test]
+ public void ErroredReverseConnection_IsRetried_OnSubsequentTick()
+ {
+ var fakeTime = new FakeTimeProvider();
+ using var server = new TestableReverseConnectServer(NUnitTelemetryContext.Create(), fakeTime);
+
+ server.AddReverseConnection(s_reverseClientUri, timeout: 5000, enabled: true);
+ ReverseConnectProperty property = server.GetReverseConnections()[s_reverseClientUri];
+
+ // Simulate that an earlier connection attempt errored
+ property.LastState = ReverseConnectState.Errored;
+ property.ServiceResult = new ServiceResult(StatusCodes.BadCommunicationError);
+
+ // On the next tick, it should attempt to reconnect (which updates ServiceResult)
+ server.InvokeOnReverseConnect(0);
+
+ Assert.That(property.ServiceResult, Is.Not.Null);
+ Assert.That(
+ property.ServiceResult!.AdditionalInfo,
+ Does.Contain("No suitable listener found"),
+ "An Errored reverse connection should be retried on subsequent ticks.");
+ }
+
+ [Test]
+ public void RejectedReverseConnection_DoesNotConnect_UntilRejectTimeoutExpires()
+ {
+ var fakeTime = new FakeTimeProvider();
+ using var server = new TestableReverseConnectServer(NUnitTelemetryContext.Create(), fakeTime);
+
+ server.AddReverseConnection(s_reverseClientUri, timeout: 5000, enabled: true);
+ ReverseConnectProperty property = server.GetReverseConnections()[s_reverseClientUri];
+
+ property.LastState = ReverseConnectState.Rejected;
+ property.RejectTime = fakeTime.GetUtcNow().UtcDateTime;
+
+ // Advance time only by 10s (default reject timeout is 60s)
+ fakeTime.Advance(TimeSpan.FromSeconds(10));
+ server.InvokeOnReverseConnect(0);
+
+ Assert.That(
+ property.LastState,
+ Is.EqualTo(ReverseConnectState.Rejected),
+ "Rejected connection must not connect before reject timeout elapses.");
+
+ // Advance time past the 60s reject timeout
+ fakeTime.Advance(TimeSpan.FromSeconds(51));
+ server.InvokeOnReverseConnect(0);
+
+ // It should now have recharged to Closed and attempted connect (becoming Errored due to no listener in test)
+ Assert.That(
+ property.LastState,
+ Is.EqualTo(ReverseConnectState.Errored),
+ "Rejected connection should attempt connect once reject timeout expires.");
+ }
+
+ [Test]
+ public void DisabledReverseConnection_DoesNotConnect()
+ {
+ var fakeTime = new FakeTimeProvider();
+ using var server = new TestableReverseConnectServer(NUnitTelemetryContext.Create(), fakeTime);
+
+ server.AddReverseConnection(s_reverseClientUri, timeout: 5000, enabled: false);
+ ReverseConnectProperty property = server.GetReverseConnections()[s_reverseClientUri];
+
+ Assert.That(property.LastState, Is.EqualTo(ReverseConnectState.Closed));
+
+ server.InvokeOnReverseConnect(0);
+
+ Assert.That(
+ property.LastState,
+ Is.EqualTo(ReverseConnectState.Closed),
+ "Disabled reverse connection must not attempt connection.");
+ }
+ }
+}