From 13667bf0135562d7e1f58e32282cae7f1cb0a615 Mon Sep 17 00:00:00 2001 From: Simon Shanks Date: Wed, 12 Aug 2026 14:46:34 +0100 Subject: [PATCH] increased test coverage --- .../ConnectionSerialisationTests.cs | 308 ++++++++++++++++++ kx.Test/Connection/KdbTlsTests.cs | 166 ++++++++++ 2 files changed, 474 insertions(+) create mode 100644 kx.Test/Connection/KdbTlsTests.cs diff --git a/kx.Test/Connection/ConnectionSerialisationTests.cs b/kx.Test/Connection/ConnectionSerialisationTests.cs index 4900026..209f5ec 100644 --- a/kx.Test/Connection/ConnectionSerialisationTests.cs +++ b/kx.Test/Connection/ConnectionSerialisationTests.cs @@ -1022,6 +1022,314 @@ private T[] CreateTestArray(Func elementBuilder, int arraySize) array[i] = elementBuilder(i); } return array; + } + + [Test] + public void ConnectionDeserialisesBigEndianIntInput() + { + const int expected = 0x01020304; + + byte[] message = CreateBigEndianMessage( + 2, + unchecked((byte)-6), + 0x01, 0x02, 0x03, 0x04); + + using (var connection = new c(_testVersionNumber)) + { + object result = connection.Deserialize(message); + + Assert.AreEqual(expected, result); + Assert.IsFalse(connection.IsSync); + Assert.IsTrue(connection.IsResponse); + } + } + + [Test] + public void ConnectionDeserialisesBigEndianLongInput() + { + const long expected = 0x0102030405060708L; + + byte[] message = CreateBigEndianMessage( + 1, + unchecked((byte)-7), + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08); + + using (var connection = new c(_testVersionNumber)) + { + object result = connection.Deserialize(message); + + Assert.AreEqual(expected, result); + Assert.IsTrue(connection.IsSync); + Assert.IsFalse(connection.IsResponse); + } + } + + [Test] + public void ConnectionDeserialisesBigEndianFloatInput() + { + const float expected = 47.25F; + + byte[] value = BitConverter.GetBytes(expected); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(value); + } + + byte[] payload = new byte[value.Length + 1]; + payload[0] = unchecked((byte)-8); + Buffer.BlockCopy(value, 0, payload, 1, value.Length); + + byte[] message = CreateBigEndianMessage(1, payload); + + using (var connection = new c(_testVersionNumber)) + { + object result = connection.Deserialize(message); + + Assert.AreEqual(expected, result); + } + } + + [Test] + public void ConnectionDeserialisesBigEndianIntArrayInput() + { + int[] expected = + { + 0x01020304, + -2, + int.MaxValue, + int.MinValue + }; + + byte[] payload = + { + 6, // q int list + 0, // attributes + 0, 0, 0, 4, // number of elements + + 0x01, 0x02, 0x03, 0x04, + 0xff, 0xff, 0xff, 0xfe, + 0x7f, 0xff, 0xff, 0xff, + 0x80, 0x00, 0x00, 0x00 + }; + + byte[] message = CreateBigEndianMessage(1, payload); + + using (var connection = new c(_testVersionNumber)) + { + int[] result = connection.Deserialize(message) as int[]; + + Assert.IsNotNull(result); + Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); + } + } + + [Test] + public void ConnectionDeserialisesLegacyDatetimeInput() + { + DateTime expected = + new DateTime(2000, 1, 2, 12, 0, 0, DateTimeKind.Unspecified); + + byte[] message = CreateLittleEndianScalar( + unchecked((byte)-15), + BitConverter.GetBytes(1.5)); + + using (var connection = new c(_testVersionNumber)) + { + object result = connection.Deserialize(message); + + Assert.AreEqual(expected, result); + } + } + + [Test] + public void ConnectionDeserialisesLegacyDatetimePositiveInfinityAsMaxDateTime() + { + byte[] message = CreateLittleEndianScalar( + unchecked((byte)-15), + BitConverter.GetBytes(double.PositiveInfinity)); + + using (var connection = new c(_testVersionNumber)) + { + object result = connection.Deserialize(message); + + Assert.AreEqual(DateTime.MaxValue, result); + } + } + + [Test] + public void ConnectionDeserialisesLegacyDatetimeNegativeInfinityAsMinimumKdbDateTime() + { + byte[] message = CreateLittleEndianScalar( + unchecked((byte)-15), + BitConverter.GetBytes(double.NegativeInfinity)); + + DateTime expected = DateTime.MinValue.AddTicks(1); + + using (var connection = new c(_testVersionNumber)) + { + object result = connection.Deserialize(message); + + Assert.AreEqual(expected, result); + } + } + + [Test] + public void ConnectionDeserialisesGenericNull() + { + byte[] message = CreateLittleEndianMessage( + 1, + 101, + 0); + + using (var connection = new c(_testVersionNumber)) + { + object result = connection.Deserialize(message); + + Assert.IsNull(result); + } + } + + [Test] + public void ConnectionThrowsKExceptionForUnsupportedFunction() + { + byte[] message = CreateLittleEndianMessage( + 1, + 101, + 1); + + using (var connection = new c(_testVersionNumber)) + { + KException exception = + Assert.Throws(() => connection.Deserialize(message)); + + Assert.AreEqual("func", exception.Message); + } + } + + [Test] + public void ConnectionSetsExpectedHeaderFlagsForSyncMessage() + { + using (var connection = new c(_testVersionNumber)) + { + byte[] message = connection.Serialize(1, 42); + + connection.Deserialize(message); + + Assert.IsTrue(connection.IsSync); + Assert.IsFalse(connection.IsResponse); + Assert.IsFalse(connection.IsCompressed); + } + } + + [Test] + public void ConnectionSetsExpectedHeaderFlagsForResponseMessage() + { + using (var connection = new c(_testVersionNumber)) + { + byte[] message = connection.Serialize(2, 42); + + connection.Deserialize(message); + + Assert.IsFalse(connection.IsSync); + Assert.IsTrue(connection.IsResponse); + Assert.IsFalse(connection.IsCompressed); + } + } + + [Test] + public void ConnectionFallsBackToUncompressedWhenDataDoesNotCompressEnough() + { + byte[] expected = new byte[10000]; + new Random(123456).NextBytes(expected); + + using (var connection = new c(_testVersionNumber)) + { + byte[] serialisedData = connection.Serialize(0, expected, true); + + // Compression was requested, but effectively random data + // cannot be reduced to the compressor's target size. + Assert.AreEqual(0, serialisedData[2]); + + byte[] result = connection.Deserialize(serialisedData) as byte[]; + + Assert.IsNotNull(result); + Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); + } + } + + [Test] + public void ConnectionCompressesHighlyCompressibleInput() + { + byte[] expected = Enumerable.Repeat((byte)42, 10000).ToArray(); + + using (var connection = new c(_testVersionNumber)) + { + byte[] serialisedData = connection.Serialize(0, expected, true); + + Assert.AreEqual(1, serialisedData[2]); + + byte[] result = connection.Deserialize(serialisedData) as byte[]; + + Assert.IsNotNull(result); + Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); + } + } + + private static byte[] CreateLittleEndianScalar( + byte type, + byte[] value) + { + byte[] payload = new byte[value.Length + 1]; + + payload[0] = type; + Buffer.BlockCopy(value, 0, payload, 1, value.Length); + + return CreateLittleEndianMessage(1, payload); + } + + private static byte[] CreateLittleEndianMessage( + byte messageType, + params byte[] payload) + { + int length = 8 + payload.Length; + byte[] message = new byte[length]; + + message[0] = 1; + message[1] = messageType; + message[2] = 0; + message[3] = 0; + + message[4] = (byte)length; + message[5] = (byte)(length >> 8); + message[6] = (byte)(length >> 16); + message[7] = (byte)(length >> 24); + + Buffer.BlockCopy(payload, 0, message, 8, payload.Length); + + return message; + } + + private static byte[] CreateBigEndianMessage( + byte messageType, + params byte[] payload) + { + int length = 8 + payload.Length; + byte[] message = new byte[length]; + + message[0] = 0; + message[1] = messageType; + message[2] = 0; + message[3] = 0; + + message[4] = (byte)(length >> 24); + message[5] = (byte)(length >> 16); + message[6] = (byte)(length >> 8); + message[7] = (byte)length; + + Buffer.BlockCopy(payload, 0, message, 8, payload.Length); + + return message; } } } diff --git a/kx.Test/Connection/KdbTlsTests.cs b/kx.Test/Connection/KdbTlsTests.cs new file mode 100644 index 0000000..460890d --- /dev/null +++ b/kx.Test/Connection/KdbTlsTests.cs @@ -0,0 +1,166 @@ +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using NUnit.Framework; + +namespace kx.Test.Connection +{ + [TestFixture] + public class KdbTlsTests + { + [Test] + public void DefaultReturnsExpectedTlsOptions() + { + const string targetHost = "localhost"; + + KdbTlsOptions options = KdbTls.Default(targetHost); + + Assert.IsTrue(options.Enabled); + Assert.AreEqual(targetHost, options.TargetHost); + Assert.IsNull(options.RemoteCertificateValidationCallback); + } + + [Test] + public void IgnoreHostnameMismatchAcceptsNoCertificateErrors() + { + KdbTlsOptions options = + KdbTls.IgnoreHostnameMismatch("localhost"); + + bool result = options.RemoteCertificateValidationCallback( + null, + null, + null, + SslPolicyErrors.None); + + Assert.IsTrue(result); + } + + [Test] + public void IgnoreHostnameMismatchAcceptsHostnameMismatch() + { + KdbTlsOptions options = + KdbTls.IgnoreHostnameMismatch("localhost"); + + bool result = options.RemoteCertificateValidationCallback( + null, + null, + null, + SslPolicyErrors.RemoteCertificateNameMismatch); + + Assert.IsTrue(result); + } + + [Test] + public void IgnoreHostnameMismatchStillRejectsChainErrorsWhenNameAlsoMismatches() + { + KdbTlsOptions options = + KdbTls.IgnoreHostnameMismatch("localhost"); + + SslPolicyErrors errors = + SslPolicyErrors.RemoteCertificateNameMismatch | + SslPolicyErrors.RemoteCertificateChainErrors; + + bool result = options.RemoteCertificateValidationCallback( + null, + null, + null, + errors); + + Assert.IsFalse(result); + } + + [Test] + public void IgnoreHostnameMismatchRejectsMissingCertificate() + { + KdbTlsOptions options = + KdbTls.IgnoreHostnameMismatch("localhost"); + + bool result = options.RemoteCertificateValidationCallback( + null, + null, + null, + SslPolicyErrors.RemoteCertificateNotAvailable); + + Assert.IsFalse(result); + } + + [Test] + public void InsecureAcceptsAllCertificateErrors() + { + KdbTlsOptions options = + KdbTls.Insecure("localhost"); + + SslPolicyErrors errors = + SslPolicyErrors.RemoteCertificateNameMismatch | + SslPolicyErrors.RemoteCertificateChainErrors | + SslPolicyErrors.RemoteCertificateNotAvailable; + + bool result = options.RemoteCertificateValidationCallback( + null, + null, + null, + errors); + + Assert.IsTrue(result); + } + + [Test] + public void DisabledHasTlsDisabled() + { + KdbTlsOptions options = KdbTlsOptions.Disabled; + + Assert.IsFalse(options.Enabled); + } + + [Test] + public void KdbTlsOptionsHasExpectedDefaults() + { + KdbTlsOptions options = new KdbTlsOptions(); + + Assert.IsFalse(options.Enabled); + Assert.IsNull(options.TargetHost); + Assert.IsNotNull(options.ClientCertificates); + Assert.AreEqual(0, options.ClientCertificates.Count); + Assert.IsNull(options.EnabledSslProtocols); + Assert.IsNull(options.CertificateRevocationCheckMode); + Assert.IsNull(options.RemoteCertificateValidationCallback); + Assert.IsNull(options.LocalCertificateSelectionCallback); + } + + [Test] + public void KdbTlsOptionsRetainsConfiguredValues() + { + RemoteCertificateValidationCallback remoteCallback = + (sender, certificate, chain, errors) => true; + + LocalCertificateSelectionCallback localCallback = + (sender, targetHost, localCertificates, + remoteCertificate, acceptableIssuers) => null; + + KdbTlsOptions options = new KdbTlsOptions + { + Enabled = true, + TargetHost = "test.example.com", + EnabledSslProtocols = SslProtocols.Tls12, + CertificateRevocationCheckMode = X509RevocationMode.Offline, + RemoteCertificateValidationCallback = remoteCallback, + LocalCertificateSelectionCallback = localCallback + }; + + Assert.IsTrue(options.Enabled); + Assert.AreEqual("test.example.com", options.TargetHost); + Assert.AreEqual( + SslProtocols.Tls12, + options.EnabledSslProtocols); + Assert.AreEqual( + X509RevocationMode.Offline, + options.CertificateRevocationCheckMode); + Assert.AreSame( + remoteCallback, + options.RemoteCertificateValidationCallback); + Assert.AreSame( + localCallback, + options.LocalCertificateSelectionCallback); + } + } +}