Skip to content

Commit

Permalink
Encode/decode property values
Browse files Browse the repository at this point in the history
  • Loading branch information
Havret committed May 1, 2024
1 parent da8bb04 commit f7f5205
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 2 deletions.
202 changes: 200 additions & 2 deletions src/ArtemisNetCoreClient/ArtemisBinaryConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ public static int WriteNullableInt64(ref byte destination, long? value)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadInt16(in ReadOnlySpan<byte> source, out short value)
private static int ReadInt16(in ReadOnlySpan<byte> source, out short value)
{
value = BinaryPrimitives.ReadInt16BigEndian(source);
return sizeof(short);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int WriteInt16(ref byte destination, short value)
private static int WriteInt16(ref byte destination, short value)
{
Unsafe.WriteUnaligned(ref destination, BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(value) : value);
return sizeof(short);
Expand Down Expand Up @@ -440,6 +440,36 @@ public static int GetNullableGuidByteCount(Guid? value)

return byteCount;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int WriteDouble(ref byte destination, double value)
{
var longValue = BitConverter.DoubleToInt64Bits(value);
return WriteInt64(ref destination, longValue);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ReadDouble(in ReadOnlySpan<byte> source, out double value)
{
var readBytes = ReadInt64(source, out var longValue);
value = BitConverter.Int64BitsToDouble(longValue);
return readBytes;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int WriteFloat(ref byte destination, float value)
{
var intValue = BitConverter.SingleToInt32Bits(value);
return WriteInt32(ref destination, intValue);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ReadFloat(in ReadOnlySpan<byte> source, out float value)
{
var readBytes = ReadInt32(source, out var intValue);
value = BitConverter.Int32BitsToSingle(intValue);
return readBytes;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int WriteNullableGuid(ref byte destination, Guid? value)
Expand Down Expand Up @@ -470,4 +500,172 @@ public static int ReadNullableGuid(in ReadOnlySpan<byte> source, out Guid? value

return readBytes;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ReadBytes(in ReadOnlySpan<byte> source, out byte[] value)
{
var readBytes = ReadInt32(source, out var length);
value = new byte[length];
source.Slice(readBytes, length).CopyTo(value);
return readBytes + length;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int WriteBytes(ref byte destination, byte[] value)
{
var offset = 0;
offset += WriteInt32(ref destination, value.Length);
var span = MemoryMarshal.CreateSpan(ref destination.GetOffset(offset), value.Length);
value.CopyTo(span);
return offset + value.Length;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetNullableObjectByteCount(object? value)
{
var byteCount = sizeof(byte);
switch (value)
{
case null:
break;
case string stringValue:
byteCount += GetSimpleStringByteCount(stringValue);
break;
case bool:
case byte:
byteCount += sizeof(byte);
break;
case byte[] bytes:
byteCount += sizeof(int) + bytes.Length;
break;
case short:
case char:
byteCount += sizeof(short);
break;
case long:
case double:
byteCount += sizeof(long);
break;
case int:
case float:
byteCount += sizeof(int);
break;
default:
throw new NotSupportedException($"Unsupported object type: {value.GetType()}");
}

return byteCount;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadNullableObject(in ReadOnlySpan<byte> source, out object? value)
{
var readBytes = ReadByte(source, out var isNotNull);
switch (isNotNull)
{
case DataConstants.Null:
value = null;
break;
case DataConstants.String:
readBytes += ReadSimpleString(source[readBytes..], out var stringValue);
value = stringValue;
break;
case DataConstants.Byte:
readBytes += ReadByte(source[readBytes..], out var byteValue);
value = byteValue;
break;
case DataConstants.Bytes:
readBytes += ReadBytes(source[readBytes..], out var bytes);
value = bytes;
break;
case DataConstants.Bool:
readBytes += ReadBool(source[readBytes..], out var boolValue);
value = boolValue;
break;
case DataConstants.Char:
readBytes += ReadInt16(source[readBytes..], out var charValue);
value = (char) charValue;
break;
case DataConstants.Double:
readBytes += ReadDouble(source[readBytes..], out var doubleValue);
value = doubleValue;
break;
case DataConstants.Float:
readBytes += ReadFloat(source[readBytes..], out var floatValue);
value = floatValue;
break;
case DataConstants.Short:
readBytes += ReadInt16(source[readBytes..], out var shortValue);
value = shortValue;
break;
case DataConstants.Int:
readBytes += ReadInt32(source[readBytes..], out var intValue);
value = intValue;
break;
case DataConstants.Long:
readBytes += ReadInt64(source[readBytes..], out var longValue);
value = longValue;
break;
default:
throw new NotSupportedException($"Unsupported object type: {isNotNull}");
}

return readBytes;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int WriteNullableObject(ref byte destination, object? value)
{
var offset = 0;
switch (value)
{
case null:
offset += WriteByte(ref destination, DataConstants.Null);
break;
case bool boolValue:
offset += WriteByte(ref destination, DataConstants.Bool);
offset += WriteBool(ref destination.GetOffset(offset), boolValue);
break;
case byte byteValue:
offset += WriteByte(ref destination, DataConstants.Byte);
offset += WriteByte(ref destination.GetOffset(offset), byteValue);
break;
case string stringValue:
offset += WriteByte(ref destination, DataConstants.String);
offset += WriteSimpleString(ref destination.GetOffset(offset), stringValue);
break;
case byte[] bytes:
offset += WriteByte(ref destination, DataConstants.Bytes);
offset += WriteBytes(ref destination.GetOffset(offset), bytes);
break;
case char charValue:
offset += WriteByte(ref destination, DataConstants.Char);
offset += WriteInt16(ref destination.GetOffset(offset), (short) charValue);
break;
case double doubleValue:
offset += WriteByte(ref destination, DataConstants.Double);
offset += WriteDouble(ref destination.GetOffset(offset), doubleValue);
break;
case float floatValue:
offset += WriteByte(ref destination, DataConstants.Float);
offset += WriteFloat(ref destination.GetOffset(offset), floatValue);
break;
case short shortValue:
offset += WriteByte(ref destination, DataConstants.Short);
offset += WriteInt16(ref destination.GetOffset(offset), shortValue);
break;
case int intValue:
offset += WriteByte(ref destination, DataConstants.Int);
offset += WriteInt32(ref destination.GetOffset(offset), intValue);
break;
case long longValue:
offset += WriteByte(ref destination, DataConstants.Long);
offset += WriteInt64(ref destination.GetOffset(offset), longValue);
break;
default:
throw new NotSupportedException($"Unsupported object type: {value.GetType()}");
}

return offset;
}
}
10 changes: 10 additions & 0 deletions src/ArtemisNetCoreClient/DataConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,14 @@ internal static class DataConstants
{
public const byte Null = 0;
public const byte NotNull = 1;
public const byte Bool = 2;
public const byte Byte = 3;
public const byte Bytes = 4;
public const byte Short = 5;
public const byte Int = 6;
public const byte Long = 7;
public const byte Float = 8;
public const byte Double = 9;
public const byte String = 10;
public const byte Char = 11;
}
49 changes: 49 additions & 0 deletions test/ArtemisNetCoreClient.Tests/ArtemisBinaryConverterSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -548,4 +548,53 @@ public void Should_decode_nullable_guid(byte[] encoded, string? expected)
Assert.Equal(expected, value?.ToString());
Assert.Equal(encoded.Length, readBytes);
}

[Theory]
[InlineData(null, new byte[] { 0 })] // null
[InlineData(true, new byte[] { 2, unchecked((byte) -1) })] // true
[InlineData(false, new byte[] { 2, 0 })] // false
[InlineData((byte) 125, new byte[] { 3, 125 })] // byte
[InlineData(new byte[] { 1, 2, 3, 4, 5, 6 }, new byte[] { 4, 0, 0, 0, 6, 1, 2, 3, 4, 5, 6 })] // byte[]
[InlineData(125, new byte[] { 6, 0, 0, 0, 125 })] // int
[InlineData((short) 125, new byte[] { 5, 0, 125 })] // short
[InlineData(long.MaxValue, new byte[] { 7, 127, 255, 255, 255, 255, 255, 255, 255 })] // long
[InlineData(12.23F, new byte[] { 8, 65, 67, unchecked((byte) -82), 20 })] // float
[InlineData(12.23D, new byte[] { 9, 64, 40, 117, unchecked((byte) -62), unchecked((byte) -113), 92, 40, unchecked((byte) -10) })] // double
[InlineData("abcdefgh", new byte[] { 10, 0, 0, 0, 16, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0 })] // string
[InlineData('a', new byte[] { 11, 0, 97 })] // char
public void Should_encode_nullable_object(object? obj, byte[] encoded)
{
// Arrange
var byteBuffer = new byte[ArtemisBinaryConverter.GetNullableObjectByteCount(obj)];

// Act
var writtenBytes = ArtemisBinaryConverter.WriteNullableObject(ref byteBuffer.AsSpan().GetReference(), obj);

// Assert
Assert.Equal(encoded, byteBuffer);
Assert.Equal(encoded.Length, writtenBytes);
}

[Theory]
[InlineData(new byte[] { 0 }, null)] // null
[InlineData(new byte[] { 2, unchecked((byte) -1) }, true)] // true
[InlineData(new byte[] { 2, 0 }, false)] // false
[InlineData(new byte[] { 3, 125 }, (byte) 125)] // byte
[InlineData(new byte[] { 4, 0, 0, 0, 6, 1, 2, 3, 4, 5, 6 }, new byte[] { 1, 2, 3, 4, 5, 6 })] // byte[]
[InlineData(new byte[] { 5, 0, 125 }, (short) 125)] // short
[InlineData(new byte[] { 6, 0, 0, 0, 125 }, 125)] // int
[InlineData(new byte[] { 7, 127, 255, 255, 255, 255, 255, 255, 255 }, long.MaxValue)] // long
[InlineData(new byte[] { 8, 65, 67, unchecked((byte) -82), 20 }, 12.23F)] // float
[InlineData(new byte[] { 9, 64, 40, 117, unchecked((byte) -62), unchecked((byte) -113), 92, 40, unchecked((byte) -10) }, 12.23D)] // double
[InlineData(new byte[] { 10, 0, 0, 0, 16, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0 }, "abcdefgh")] // string
[InlineData(new byte[] { 11, 0, 97 }, 'a')] // char
public void Should_decode_nullable_object(byte[] encoded, object? expected)
{
// Act
var readBytes = ArtemisBinaryConverter.ReadNullableObject(encoded, out var value);

// Assert
Assert.Equal(expected, value);
Assert.Equal(encoded.Length, readBytes);
}
}

0 comments on commit f7f5205

Please sign in to comment.