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
177 changes: 177 additions & 0 deletions kx.Test/Connection/ConnectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
#if NETCOREAPP3_1_OR_GREATER
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
Expand Down Expand Up @@ -330,6 +331,177 @@ public async Task ProtectedParameterlessWriteAsyncWritesRequestedBytes()
}
}

[Test]
public void SynchronousReadDecompressesMessage()
{
string expected = new string('x', 5000);
byte[] message = CreateCompressedMessage(expected);

using (var stream = new MemoryStream(message))
using (var connection = new TestConnection(stream))
{
connection.k0();

Assert.IsTrue(connection.IsCompressed);
Assert.AreEqual(expected, connection.ExposedReadObject());
}
}

[Test]
public async Task AsynchronousReadDecompressesMessage()
{
string expected = new string('x', 5000);
byte[] message = CreateCompressedMessage(expected);

using (var stream = new MemoryStream(message))
using (var connection = new TestConnection(stream))
{
await connection.k0Async();

Assert.IsTrue(connection.IsCompressed);
Assert.AreEqual(expected, connection.ExposedReadObject());
}
}

[TestCase(1)]
[TestCase(2)]
public void SynchronousMessageWritesExpectedMessageType(int messageType)
{
const string expected = "payload";
using (var stream = new MemoryStream())
using (var connection = new c(stream))
{
if (messageType == 1)
{
connection.kn(expected);
}
else
{
connection.kr(expected);
}

byte[] message = stream.ToArray();
Assert.AreEqual(messageType, message[1]);
Assert.AreEqual(expected, connection.Deserialize(message));
}
}

[Test]
public void ProtectedReadInt32ReturnsExpectedValue()
{
const int expected = 42;
using (var connection = new TestConnection())
{
byte[] message = connection.Serialize(1, expected);
connection.Deserialize(message);
connection.ExposedReadPosition = 9;

Assert.AreEqual(expected, connection.ExposedReadInt32());
}
}

[Test]
public void SynchronousReadThrowsWhenStreamEndsBeforeHeader()
{
using (var connection = new c(new MemoryStream()))
{
KException exception = Assert.Throws<KException>(() => connection.k0());

Assert.AreEqual("read", exception.Message);
}
}

[Test]
public void AsynchronousReadThrowsWhenStreamEndsBeforeHeader()
{
using (var connection = new c(new MemoryStream()))
{
KException exception = Assert.ThrowsAsync<KException>(
async () => await connection.k0Async());

Assert.AreEqual("read", exception.Message);
}
}

[Test]
public void StreamReadThrowsKdbExceptionMessage()
{
const string expected = "KDB+_Error";
byte[] text = Encoding.ASCII.GetBytes(expected);
byte[] message = new byte[10 + text.Length];
message[0] = 1;
message[1] = 1;
Buffer.BlockCopy(BitConverter.GetBytes(message.Length), 0, message, 4, 4);
message[8] = 128;
Buffer.BlockCopy(text, 0, message, 9, text.Length);

using (var connection = new c(new MemoryStream(message)))
{
KException exception = Assert.Throws<KException>(() => connection.k0());

Assert.AreEqual(expected, exception.Message);
}
}

[Test]
public void NullTemporalValuesRoundTrip()
{
object[] expectedValues =
{
new DateTime(0L),
new c.KTimespan(long.MinValue),
new TimeSpan(long.MinValue)
};

using (var connection = new c(3))
{
foreach (object expected in expectedValues)
{
byte[] message = connection.Serialize(1, expected);

Assert.AreEqual(expected, connection.Deserialize(message));
}
}
}

[Test]
public void StringSerializationStopsAtEmbeddedNullCharacter()
{
const string expected = "before";
using (var connection = new c(3))
{
byte[] message = connection.Serialize(1, expected + "\0after");

Assert.AreEqual(expected, connection.Deserialize(message));
}
}

[Test]
public void LegacyDatetimeNullDeserializesToDateTimeNull()
{
byte[] message = new byte[17];
message[0] = 1;
message[1] = 1;
Buffer.BlockCopy(BitConverter.GetBytes(message.Length), 0, message, 4, 4);
message[8] = unchecked((byte)-15);
Buffer.BlockCopy(BitConverter.GetBytes(double.NaN), 0, message, 9, 8);

using (var connection = new c(3))
{
Assert.AreEqual(new DateTime(0L), connection.Deserialize(message));
}
}

private static byte[] CreateCompressedMessage(string value)
{
using (var serializer = new c(3))
{
byte[] message = serializer.Serialize(1, value, true);
Assert.AreEqual(1, message[2], "Test input was not compressed.");
return message;
}
}

private sealed class TestConnection : c
{
internal TestConnection()
Expand All @@ -356,6 +528,11 @@ internal object ExposedReadObject()
return ReadObject();
}

internal int ExposedReadInt32()
{
return ReadInt32();
}

internal Task ExposedWriteAsync(byte[] bytes, int number)
{
return WriteAsync(bytes, number);
Expand Down
8 changes: 8 additions & 0 deletions kx.Test/Types/DateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ public void DateToDateTimeReturnsExpectedDateTimeIfValueIsInt32Min()
Assert.AreEqual(expected, date.DateTime());
}

[Test]
public void DateToDateTimeReturnsNullDateTimeIfValueIsInt32MinValue()
{
c.Date date = new c.Date(int.MinValue);

Assert.AreEqual(new DateTime(0L), date.DateTime());
}

[Test]
public void DateToDateTimeReturnsExpectedDateTimeIfValueIsInt32Max()
{
Expand Down
13 changes: 8 additions & 5 deletions kx/c.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
/// This class is essentially a serializer/deserializer of .NET types
/// to/from the KDB+ IPC wire format, enabling remote method invocation in KDB+ via TCP/IP.
/// </remarks>
public class c : IDisposable

Check warning on line 22 in kx/c.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'c' only contains lower-cased ascii characters. Such names may become reserved for the language.

Check warning on line 22 in kx/c.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'c' only contains lower-cased ascii characters. Such names may become reserved for the language.
{
private readonly Socket _socket;

Expand Down Expand Up @@ -222,8 +222,8 @@
break;
}
_socket.Connect(host, port);
_isLoopback = _socket.RemoteEndPoint is IPEndPoint &&
IPAddress.IsLoopback((_socket.RemoteEndPoint as IPEndPoint).Address);
_isLoopback = IPAddress.IsLoopback(
((IPEndPoint)_socket.RemoteEndPoint).Address);
}
_clientStream = new NetworkStream(_socket);
if (tlsOptions != null && tlsOptions.Enabled)
Expand Down Expand Up @@ -1581,7 +1581,12 @@

private void w(string s)
{
_writePosition += e.GetBytes(s,0,s.Length,_writeBuffer,_writePosition);
int length = s.IndexOf('\0');
if (length < 0)
{
length = s.Length;
}
_writePosition += e.GetBytes(s,0,length,_writeBuffer,_writePosition);
_writeBuffer[_writePosition++] = 0;
}

Expand Down Expand Up @@ -1708,8 +1713,6 @@
w(n(x));
switch (t)
{
case 3:
break;
case 0:
{
foreach (object obj in (object[])x)
Expand Down