diff --git a/src/Cassandra.Tests/Cassandra.Tests.csproj b/src/Cassandra.Tests/Cassandra.Tests.csproj
index cc260a25e..3bd6869a6 100644
--- a/src/Cassandra.Tests/Cassandra.Tests.csproj
+++ b/src/Cassandra.Tests/Cassandra.Tests.csproj
@@ -11,15 +11,23 @@
true
Cassandra.Tests
true
- 7.1
+ latest
-
+
$(DefineConstants);NETCOREAPP
+ $(NoWarn);SYSLIB0051;SYSLIB0012;SYSLIB0039;CA1416
-
+
TargetFramework=netstandard2.0
+
+
+
+ TargetFramework=net8.0
+
+
+
TargetFramework=netstandard2.0
diff --git a/src/Cassandra.Tests/Connections/TlsSessionTicketCacheTests.cs b/src/Cassandra.Tests/Connections/TlsSessionTicketCacheTests.cs
new file mode 100644
index 000000000..9823db906
--- /dev/null
+++ b/src/Cassandra.Tests/Connections/TlsSessionTicketCacheTests.cs
@@ -0,0 +1,97 @@
+//
+// Copyright (C) DataStax Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Security.Authentication;
+using Cassandra.Connections;
+using NUnit.Framework;
+
+namespace Cassandra.Tests.Connections
+{
+ [TestFixture]
+ public class TlsSessionTicketCacheTests
+ {
+#if NET8_0_OR_GREATER
+ [Test]
+ public void GetAuthenticationOptions_Should_SetAllowTlsResume_True_By_Default()
+ {
+ var cache = new TlsSessionTicketCache();
+ var sslOptions = new SSLOptions();
+ var authOpts = cache.GetAuthenticationOptions("myhost.example.com", sslOptions);
+
+ Assert.That(authOpts.AllowTlsResume, Is.True);
+ Assert.That(authOpts.TargetHost, Is.EqualTo("myhost.example.com"));
+ Assert.That(authOpts.EnabledSslProtocols, Is.EqualTo(SslProtocols.Tls));
+ Assert.That(authOpts.CertificateRevocationCheckMode,
+ Is.EqualTo(System.Security.Cryptography.X509Certificates.X509RevocationMode.NoCheck));
+ }
+
+ [Test]
+ public void GetAuthenticationOptions_Should_SetAllowTlsResume_False_When_Disabled()
+ {
+ var cache = new TlsSessionTicketCache();
+ var sslOptions = new SSLOptions().SetEnableSessionResumption(false);
+ var authOpts = cache.GetAuthenticationOptions("myhost.example.com", sslOptions);
+
+ Assert.That(authOpts.AllowTlsResume, Is.False);
+ }
+
+ [Test]
+ public void GetAuthenticationOptions_Should_Set_RevocationMode_Online_When_CheckRevocation_True()
+ {
+ var cache = new TlsSessionTicketCache();
+ var sslOptions = new SSLOptions().SetCertificateRevocationCheck(true);
+ var authOpts = cache.GetAuthenticationOptions("myhost.example.com", sslOptions);
+
+ Assert.That(authOpts.CertificateRevocationCheckMode,
+ Is.EqualTo(System.Security.Cryptography.X509Certificates.X509RevocationMode.Online));
+ }
+
+ [Test]
+ public void GetAuthenticationOptions_Should_PreserveRemoteCertValidationCallback()
+ {
+ var cache = new TlsSessionTicketCache();
+ System.Net.Security.RemoteCertificateValidationCallback customCallback =
+ (sender, cert, chain, errors) => true;
+ var sslOptions = new SSLOptions().SetRemoteCertValidationCallback(customCallback);
+ var authOpts = cache.GetAuthenticationOptions("myhost.example.com", sslOptions);
+
+ Assert.That(authOpts.RemoteCertificateValidationCallback, Is.SameAs(customCallback));
+ }
+
+ [Test]
+ public void GetAuthenticationOptions_Should_Return_Same_Instance_For_Same_Host()
+ {
+ var cache = new TlsSessionTicketCache();
+ var sslOptions = new SSLOptions();
+ var opts1 = cache.GetAuthenticationOptions("host1.example.com", sslOptions);
+ var opts2 = cache.GetAuthenticationOptions("host1.example.com", sslOptions);
+
+ Assert.That(opts1, Is.SameAs(opts2));
+ }
+
+ [Test]
+ public void GetAuthenticationOptions_Should_Return_Different_Instances_For_Different_Hosts()
+ {
+ var cache = new TlsSessionTicketCache();
+ var sslOptions = new SSLOptions();
+ var opts1 = cache.GetAuthenticationOptions("host1.example.com", sslOptions);
+ var opts2 = cache.GetAuthenticationOptions("host2.example.com", sslOptions);
+
+ Assert.That(opts1, Is.Not.SameAs(opts2));
+ }
+#endif
+ }
+}
diff --git a/src/Cassandra.Tests/EndianBitConverterTests.cs b/src/Cassandra.Tests/EndianBitConverterTests.cs
index b6d03f5c1..93d3e4458 100644
--- a/src/Cassandra.Tests/EndianBitConverterTests.cs
+++ b/src/Cassandra.Tests/EndianBitConverterTests.cs
@@ -40,10 +40,10 @@ public void ToInt32_SetBytes_Test()
EndianBitConverter.SetBytes(false, buffer, 0, v.Item1);
CollectionAssert.AreEqual(v.Item2, buffer);
EndianBitConverter.SetBytes(true, buffer, 0, v.Item1);
- CollectionAssert.AreEqual(v.Item2.Reverse(), buffer);
+ CollectionAssert.AreEqual(v.Item2.AsEnumerable().Reverse(), buffer);
var decoded = EndianBitConverter.ToInt32(false, v.Item2, 0);
Assert.AreEqual(v.Item1, decoded);
- decoded = EndianBitConverter.ToInt32(true, v.Item2.Reverse().ToArray(), 0);
+ decoded = EndianBitConverter.ToInt32(true, v.Item2.AsEnumerable().Reverse().ToArray(), 0);
Assert.AreEqual(v.Item1, decoded);
}
}
@@ -65,10 +65,10 @@ public void ToDouble_SetBytes_Test()
EndianBitConverter.SetBytes(false, buffer, 0, v.Item1);
CollectionAssert.AreEqual(v.Item2, buffer);
EndianBitConverter.SetBytes(true, buffer, 0, v.Item1);
- CollectionAssert.AreEqual(v.Item2.Reverse(), buffer);
+ CollectionAssert.AreEqual(v.Item2.AsEnumerable().Reverse(), buffer);
var decoded = EndianBitConverter.ToDouble(false, v.Item2, 0);
Assert.AreEqual(v.Item1, decoded);
- decoded = EndianBitConverter.ToDouble(true, v.Item2.Reverse().ToArray(), 0);
+ decoded = EndianBitConverter.ToDouble(true, v.Item2.AsEnumerable().Reverse().ToArray(), 0);
Assert.AreEqual(v.Item1, decoded);
}
}
diff --git a/src/Cassandra.Tests/IOUnitTests.cs b/src/Cassandra.Tests/IOUnitTests.cs
index 26c2227d9..5cf5be12c 100644
--- a/src/Cassandra.Tests/IOUnitTests.cs
+++ b/src/Cassandra.Tests/IOUnitTests.cs
@@ -68,7 +68,7 @@ public void OperationState_Can_Concurrently_Get_Timeout_And_Response()
if ((counter++) % 2 == 0)
{
//invert order
- actions = actions.Reverse().ToArray();
+ actions = actions.AsEnumerable().Reverse().ToArray();
}
TestHelper.ParallelInvoke(actions);
}, times);
diff --git a/src/Cassandra.Tests/SSLOptionsTests.cs b/src/Cassandra.Tests/SSLOptionsTests.cs
new file mode 100644
index 000000000..1403835df
--- /dev/null
+++ b/src/Cassandra.Tests/SSLOptionsTests.cs
@@ -0,0 +1,46 @@
+//
+// Copyright (C) DataStax Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using NUnit.Framework;
+
+namespace Cassandra.Tests
+{
+ [TestFixture]
+ public class SSLOptionsTests
+ {
+ [Test]
+ public void EnableSessionResumption_Should_Default_To_True()
+ {
+ var options = new SSLOptions();
+ Assert.That(options.EnableSessionResumption, Is.True);
+ }
+
+ [Test]
+ public void SetEnableSessionResumption_Should_Set_Value_False()
+ {
+ var options = new SSLOptions().SetEnableSessionResumption(false);
+ Assert.That(options.EnableSessionResumption, Is.False);
+ }
+
+ [Test]
+ public void SetEnableSessionResumption_Should_Return_Same_Instance()
+ {
+ var options = new SSLOptions();
+ var returned = options.SetEnableSessionResumption(true);
+ Assert.That(returned, Is.SameAs(options));
+ }
+ }
+}
diff --git a/src/Cassandra.Tests/TargetTests.cs b/src/Cassandra.Tests/TargetTests.cs
index 12f899125..51be2ed85 100644
--- a/src/Cassandra.Tests/TargetTests.cs
+++ b/src/Cassandra.Tests/TargetTests.cs
@@ -33,7 +33,11 @@ public void Should_TargetNetstandard15_When_TestsTargetNetcore20()
.GetCustomAttribute()?
.FrameworkName;
+#if NET8_0_OR_GREATER
+ Assert.AreEqual(".NETCoreApp,Version=v8.0", framework);
+#else
Assert.AreEqual(".NETStandard,Version=v2.0", framework);
+#endif
}
#else
[Test]
diff --git a/src/Cassandra.Tests/TokenTests.cs b/src/Cassandra.Tests/TokenTests.cs
index 94caf4a17..647dfb267 100644
--- a/src/Cassandra.Tests/TokenTests.cs
+++ b/src/Cassandra.Tests/TokenTests.cs
@@ -572,8 +572,7 @@ public void Should_UpdateKeyspacesAndTokenMapCorrectly_When_MultipleThreadsCalli
}
}
}
- else
- if (j % 2 == 0)
+ else if (j % 2 == 0)
{
if (bag.TryTake(out var ksName))
{
diff --git a/src/Cassandra/Cassandra.csproj b/src/Cassandra/Cassandra.csproj
index d6a54c203..dc4d83efa 100644
--- a/src/Cassandra/Cassandra.csproj
+++ b/src/Cassandra/Cassandra.csproj
@@ -7,7 +7,7 @@
3.22.0.2
false
DataStax and ScyllaDB
- netstandard2.0
+ netstandard2.0;net8.0
$(NoWarn);1591
true
NU1901;NU1902;NU1903;NU1904
@@ -23,11 +23,13 @@
LICENSE.md
https://github.com/scylladb/csharp-driver
https://github.com/scylladb/csharp-driver
- 7.1
+ latest
false
-
+
$(DefineConstants);NETCOREAPP
+
+ $(NoWarn);SYSLIB0051;SYSLIB0012;SYSLIB0039;CA1416
diff --git a/src/Cassandra/Configuration.cs b/src/Cassandra/Configuration.cs
index 29d42bd7b..c3aad0c9c 100644
--- a/src/Cassandra/Configuration.cs
+++ b/src/Cassandra/Configuration.cs
@@ -226,6 +226,12 @@ public class Configuration
internal IServerNameResolver ServerNameResolver { get; }
+ ///
+ /// Per-cluster TLS session ticket cache used for TLS session resumption.
+ /// Null when SSL is not configured.
+ ///
+ internal TlsSessionTicketCache TlsSessionTicketCache { get; }
+
internal Configuration() :
this(Policies.DefaultPolicies,
new ProtocolOptions(),
@@ -361,6 +367,11 @@ internal Configuration(Policies policies,
// to create the instance.
BufferPool = new RecyclableMemoryStreamManager(16 * 1024, 256 * 1024, ProtocolOptions.MaximumFrameLength);
Timer = new HashedWheelTimer();
+
+ if (ProtocolOptions.SslOptions != null)
+ {
+ TlsSessionTicketCache = new TlsSessionTicketCache();
+ }
}
///
diff --git a/src/Cassandra/Connections/Connection.cs b/src/Cassandra/Connections/Connection.cs
index 0387dfd74..1e5926a92 100644
--- a/src/Cassandra/Connections/Connection.cs
+++ b/src/Cassandra/Connections/Connection.cs
@@ -180,7 +180,7 @@ internal Connection(
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_startupRequestFactory = startupRequestFactory ?? throw new ArgumentNullException(nameof(startupRequestFactory));
_heartBeatInterval = configuration.GetHeartBeatInterval() ?? 0;
- _tcpSocket = new TcpSocket(endPoint, configuration.SocketOptions, configuration.ProtocolOptions.SslOptions);
+ _tcpSocket = new TcpSocket(endPoint, configuration.SocketOptions, configuration.ProtocolOptions.SslOptions, configuration.TlsSessionTicketCache);
_idleTimer = new Timer(IdleTimeoutHandler, null, Timeout.Infinite, Timeout.Infinite);
_connectionObserver = connectionObserver;
_timerEnabled = configuration.MetricsEnabled
diff --git a/src/Cassandra/Connections/TcpSocket.cs b/src/Cassandra/Connections/TcpSocket.cs
index 554fa877a..9d7dd751b 100644
--- a/src/Cassandra/Connections/TcpSocket.cs
+++ b/src/Cassandra/Connections/TcpSocket.cs
@@ -23,6 +23,9 @@
using System.Threading.Tasks;
using Cassandra.Tasks;
using Microsoft.IO;
+#if NET8_0_OR_GREATER
+using System.Security.Authentication;
+#endif
namespace Cassandra.Connections
{
@@ -52,6 +55,12 @@ internal class TcpSocket : ITcpSocket
public SSLOptions SSLOptions { get; set; }
+ ///
+ /// The per-cluster TLS session ticket cache for session resumption.
+ /// May be null if SSL is not configured.
+ ///
+ internal TlsSessionTicketCache TlsSessionTicketCache { get; set; }
+
///
/// Event that gets fired when new data is received.
///
@@ -72,11 +81,12 @@ internal class TcpSocket : ITcpSocket
///
/// Creates a new instance of TcpSocket using the endpoint and options provided.
///
- public TcpSocket(IConnectionEndPoint endPoint, SocketOptions options, SSLOptions sslOptions)
+ public TcpSocket(IConnectionEndPoint endPoint, SocketOptions options, SSLOptions sslOptions, TlsSessionTicketCache tlsSessionTicketCache = null)
{
EndPoint = endPoint;
Options = options;
SSLOptions = sslOptions;
+ TlsSessionTicketCache = tlsSessionTicketCache;
_socket = new Socket(EndPoint.SocketIpEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
{
@@ -199,6 +209,36 @@ private async Task ConnectSsl()
TcpSocket.Logger.Verbose("Socket connected, starting SSL client authentication");
//Stream mode: not the most performant but it has ssl support
TcpSocket.Logger.Verbose("Starting SSL authentication");
+
+#if NET8_0_OR_GREATER
+ var serverName = await EndPoint.GetServerNameAsync().ConfigureAwait(false);
+ var sslStream = new SslStream(new NetworkStream(_socket), false);
+ _socketStream = sslStream;
+
+ // Use a timer to ensure that it does callback
+ var tcs = TaskHelper.TaskCompletionSourceWithTimeout(
+ Options.ConnectTimeoutMillis,
+ () => new TimeoutException("The timeout period elapsed prior to completion of SSL authentication operation."));
+
+ var authOptions = TlsSessionTicketCache.GetAuthenticationOptions(serverName, SSLOptions);
+ TcpSocket.Logger.Verbose("Using SslClientAuthenticationOptions with AllowTlsResume={0}", authOptions.AllowTlsResume);
+
+ sslStream.AuthenticateAsClientAsync(authOptions)
+ .ContinueWith(t =>
+ {
+ if (t.Exception != null)
+ {
+ t.Exception.Handle(_ => true);
+ tcs.TrySetException(t.Exception.InnerException);
+ return;
+ }
+ tcs.TrySetResult(true);
+ }, TaskContinuationOptions.ExecuteSynchronously)
+ .Forget();
+
+ await tcs.Task.ConfigureAwait(false);
+ TcpSocket.Logger.Verbose("SSL authentication successful");
+#else
var sslStream = new SslStream(new NetworkStream(_socket), false, SSLOptions.RemoteCertValidationCallback, null);
_socketStream = sslStream;
// Use a timer to ensure that it does callback
@@ -226,6 +266,7 @@ private async Task ConnectSsl()
await tcs.Task.ConfigureAwait(false);
TcpSocket.Logger.Verbose("SSL authentication successful");
+#endif
ReceiveAsync();
return true;
}
diff --git a/src/Cassandra/Connections/TlsSessionTicketCache.cs b/src/Cassandra/Connections/TlsSessionTicketCache.cs
new file mode 100644
index 000000000..83a627ff3
--- /dev/null
+++ b/src/Cassandra/Connections/TlsSessionTicketCache.cs
@@ -0,0 +1,78 @@
+//
+// Copyright (C) DataStax Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#if NET8_0_OR_GREATER
+using System.Collections.Concurrent;
+using System.Net.Security;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+#endif
+
+namespace Cassandra.Connections
+{
+ ///
+ /// Provides TLS session ticket caching for a Cluster instance.
+ /// On .NET 8.0+, this uses SslClientAuthenticationOptions with
+ /// AllowTlsResume = true to enable TLS session resumption,
+ /// reducing handshake overhead for subsequent connections to the same host.
+ /// The same SslClientAuthenticationOptions instance is reused for all
+ /// connections to a given server name, which is required by .NET for session
+ /// ticket reuse.
+ /// On older runtimes (netstandard2.0), this class is a no-op placeholder.
+ ///
+ internal class TlsSessionTicketCache
+ {
+ private static readonly Logger Logger = new Logger(typeof(TlsSessionTicketCache));
+
+#if NET8_0_OR_GREATER
+ private readonly ConcurrentDictionary _cache =
+ new ConcurrentDictionary();
+
+ ///
+ /// Returns a cached for the given server name,
+ /// creating one on first access. The same instance is returned for subsequent calls with
+ /// the same , which is required by .NET for TLS session resumption.
+ ///
+ /// The target host name for TLS SNI and certificate validation.
+ /// The SSL options configured on the cluster.
+ /// A cached instance.
+ public SslClientAuthenticationOptions GetAuthenticationOptions(string serverName, SSLOptions sslOptions)
+ {
+ return _cache.GetOrAdd(serverName, key =>
+ {
+ var options = new SslClientAuthenticationOptions
+ {
+ TargetHost = key,
+ ClientCertificates = sslOptions.CertificateCollection,
+ EnabledSslProtocols = sslOptions.SslProtocol,
+ CertificateRevocationCheckMode = sslOptions.CheckCertificateRevocation
+ ? X509RevocationMode.Online
+ : X509RevocationMode.NoCheck,
+ RemoteCertificateValidationCallback = sslOptions.RemoteCertValidationCallback,
+ AllowTlsResume = sslOptions.EnableSessionResumption
+ };
+
+ Logger.Verbose(
+ "Created SslClientAuthenticationOptions for host '{0}' (AllowTlsResume={1})",
+ key,
+ options.AllowTlsResume);
+
+ return options;
+ });
+ }
+#endif
+ }
+}
diff --git a/src/Cassandra/Data/Linq/CqlExpressionVisitor.cs b/src/Cassandra/Data/Linq/CqlExpressionVisitor.cs
index a6e23b6b4..44af61644 100644
--- a/src/Cassandra/Data/Linq/CqlExpressionVisitor.cs
+++ b/src/Cassandra/Data/Linq/CqlExpressionVisitor.cs
@@ -41,6 +41,39 @@ internal class CqlExpressionVisitor : ExpressionVisitor
private static readonly string Utf8MaxValue = Encoding.UTF8.GetString(new byte[] { 0xF4, 0x8F, 0xBF, 0xBF });
+ ///
+ /// Evaluates an expression by compiling it into a typed Func<T> delegate
+ /// and invoking it directly. This avoids:
+ /// 1. DynamicInvoke - which throws NotSupportedException on .NET 8+ for compiled delegates
+ /// 2. Expression.Convert to object - which can throw InvalidProgramException on .NET 9+
+ ///
+ private static object EvaluateExpressionValue(Expression expression)
+ {
+ // Ref structs (e.g. ReadOnlySpan, Span) cannot be used as generic type arguments.
+ // In .NET 9+, Enumerable.Contains() has overloads that accept ReadOnlySpan, so the
+ // collection argument in the expression tree may be wrapped in a Convert node to
+ // ReadOnlySpan. Unwrap it to recover the underlying enumerable.
+ if (expression.Type.IsByRefLike)
+ {
+ if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert)
+ {
+ return EvaluateExpressionValue(unary.Operand);
+ }
+ throw new InvalidOperationException(
+ $"Cannot evaluate expression of by-ref-like type '{expression.Type}'. " +
+ "This may be caused by a ReadOnlySpan overload being selected (e.g. Enumerable.Contains in .NET 9+).");
+ }
+ return EvalMethod.MakeGenericMethod(expression.Type).Invoke(null, new object[] { expression });
+ }
+
+ private static readonly MethodInfo EvalMethod =
+ typeof(CqlExpressionVisitor).GetMethod(nameof(CompileAndInvoke), BindingFlags.NonPublic | BindingFlags.Static);
+
+ private static object CompileAndInvoke(Expression expression)
+ {
+ return Expression.Lambda>(expression).Compile().Invoke();
+ }
+
private static readonly HashSet CqlUnsupTags = new HashSet
{
ExpressionType.Or,
@@ -577,7 +610,7 @@ private Expression AddProjection(Expression node, PocoColumn column = null)
}
else
{
- value = Expression.Lambda(node).Compile().DynamicInvoke();
+ value = EvaluateExpressionValue(node);
}
if (column == null)
{
@@ -607,7 +640,7 @@ private Expression EvaluateConditionFunction(MethodCallExpression node)
case nameof(string.StartsWith) when node.Method.DeclaringType == typeof(string):
Visit(node.Object);
var startsWithArgument = node.Arguments[0];
- var startString = (string)Expression.Lambda(startsWithArgument).Compile().DynamicInvoke();
+ var startString = (string)EvaluateExpressionValue(startsWithArgument);
var endString = startString + CqlExpressionVisitor.Utf8MaxValue;
// Create 2 conditions, ie: WHERE col1 >= startString AND col2 < endString
var column = condition.Column;
@@ -650,7 +683,7 @@ private Expression EvaluateConditionFunction(MethodCallExpression node)
return node;
}
// Try to invoke to obtain the parameter value
- condition.SetParameter(Expression.Lambda(node).Compile().DynamicInvoke());
+ condition.SetParameter(EvaluateExpressionValue(node));
return node;
}
@@ -678,7 +711,7 @@ private void EvaluateContainsMethod(MethodCallExpression node)
EvaluateCompositeColumn(columnExpression);
}
- var values = Expression.Lambda(parameterExpression).Compile().DynamicInvoke() as IEnumerable;
+ var values = EvaluateExpressionValue(parameterExpression) as IEnumerable;
if (values == null)
{
throw new InvalidOperationException("Contains parameter must be IEnumerable");
@@ -751,7 +784,7 @@ private bool EvaluateOperatorMethod(MethodCallExpression node)
}
// Use the last argument (valid for maps and list/sets)
var argument = node.Arguments[node.Arguments.Count - 1];
- var value = Expression.Lambda(argument).Compile().DynamicInvoke();
+ var value = EvaluateExpressionValue(argument);
_projections.Add(Tuple.Create(column, value, expressionType));
return true;
}
@@ -788,7 +821,7 @@ protected override Expression VisitUnary(UnaryExpression node)
}
else
{
- var val = Expression.Lambda(node).Compile().DynamicInvoke();
+ var val = EvaluateExpressionValue(node);
condition.SetParameter(val);
}
return node;
@@ -803,7 +836,7 @@ protected override Expression VisitUnary(UnaryExpression node)
}
if (column != null && column.IsCounter)
{
- var value = Expression.Lambda(node).Compile().DynamicInvoke();
+ var value = EvaluateExpressionValue(node);
if (!(value is long || value is int))
{
throw new ArgumentException("Only Int64 and Int32 values are supported as counter increment of decrement values");
@@ -876,7 +909,7 @@ protected override Expression VisitBinary(BinaryExpression node)
if (!CqlExpressionVisitor.CqlUnsupTags.Contains(node.NodeType))
{
- condition.SetParameter(Expression.Lambda(node).Compile().DynamicInvoke());
+ condition.SetParameter(EvaluateExpressionValue(node));
return node;
}
}
@@ -1115,7 +1148,7 @@ private static object GetClosureValue(MemberExpression node)
}
else
{
- value = Expression.Lambda(node).Compile().DynamicInvoke();
+ value = EvaluateExpressionValue(node);
}
return value;
}
diff --git a/src/Cassandra/Helpers/PlatformHelper.cs b/src/Cassandra/Helpers/PlatformHelper.cs
index 01084c1da..3f0d77ddd 100644
--- a/src/Cassandra/Helpers/PlatformHelper.cs
+++ b/src/Cassandra/Helpers/PlatformHelper.cs
@@ -33,7 +33,9 @@ public static bool IsKerberosSupported()
public static string GetTargetFramework()
{
-#if NETSTANDARD2_0
+#if NET8_0_OR_GREATER
+ return ".NET 8.0+";
+#elif NETSTANDARD2_0
return ".NET Standard 2.0";
#else
return null;
diff --git a/src/Cassandra/SSLOptions.cs b/src/Cassandra/SSLOptions.cs
index b2aef4ea8..f4d49c735 100644
--- a/src/Cassandra/SSLOptions.cs
+++ b/src/Cassandra/SSLOptions.cs
@@ -33,6 +33,7 @@ public class SSLOptions
private bool _checkCertificateRevocation;
private X509CertificateCollection _certificateCollection = new X509CertificateCollection();
private Func _hostNameResolver = GetHostName;
+ private bool _enableSessionResumption = true;
///
/// Verifies Cassandra host SSL certificate used for authentication.
@@ -75,21 +76,32 @@ public X509CertificateCollection CertificateCollection
}
///
- /// Creates SSLOptions with default values.
+ /// Gets whether TLS session resumption (session tickets) is enabled.
+ /// When enabled, subsequent TLS connections to the same host can reuse
+ /// previously negotiated session parameters, reducing handshake overhead.
+ /// Effective on .NET 8.0 and later. Defaults to true.
+ ///
+ public bool EnableSessionResumption
+ {
+ get { return _enableSessionResumption; }
+ }
+
+ ///
+ /// Creates SSLOptions with default values.
///
public SSLOptions()
{
}
///
- /// Creates SSL options used for SSL connections with Casandra hosts.
+ /// Creates SSL options used for SSL connections with Casandra hosts.
///
/// type of SSL protocol, default set to Tls.
/// specifies whether the certificate revocation list is checked during connection authentication.
/// verifies Cassandra host SSL certificate used for authentication.
///
- /// Default RemoteCertificateValidationCallback won't establish a connection if any error will occur.
- ///
+ /// Default RemoteCertificateValidationCallback won't establish a connection if any error will occur.
+ ///
///
public SSLOptions(SslProtocols sslProtocol, bool checkCertificateRevocation, RemoteCertificateValidationCallback remoteCertValidationCallback)
{
@@ -134,6 +146,18 @@ public SSLOptions SetRemoteCertValidationCallback(RemoteCertificateValidationCal
return this;
}
+ ///
+ /// Sets whether TLS session resumption (session tickets) is enabled.
+ /// When enabled, subsequent TLS connections to the same host can reuse
+ /// previously negotiated session parameters, reducing handshake overhead.
+ /// Effective on .NET 8.0 and later. Defaults to true.
+ ///
+ public SSLOptions SetEnableSessionResumption(bool flag)
+ {
+ _enableSessionResumption = flag;
+ return this;
+ }
+
private static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,