Skip to content

Instantly share code, notes, and snippets.

@thenameless314159
Created January 9, 2020 13:03
Show Gist options
  • Save thenameless314159/38c916cfe1355b76a50b84759f1be6b3 to your computer and use it in GitHub Desktop.
Save thenameless314159/38c916cfe1355b76a50b84759f1be6b3 to your computer and use it in GitHub Desktop.
#if NETCOREAPP3_1
using System;
using System.Linq;
using System.Text;
using System.Buffers;
using NUnit.Framework;
using System.Reflection;
using System.Buffers.Binary;
using static FastExpressionCompiler.LightExpression.Expression;
// ReSharper disable InconsistentNaming
namespace FastExpressionCompiler.LightExpression.IssueTests
{
internal delegate bool DeserializerDlg<in T>(ref ReadOnlySequence<byte> seq, T value, out long bytesRead);
public class Issue237_Serialization_InvalidProgramException
{
private static readonly MethodInfo _tryRead = typeof(ReaderExtensions).GetMethod(nameof(ReaderExtensions.TryReadValue));
private static readonly MethodInfo _tryDeserialize = typeof(Serializer).GetMethod(nameof(Serializer.TryDeserializeValues));
[SetUp]
public void Setup_ShouldCompileExpressions()
{
var reader = Variable(typeof(SequenceReader<byte>), "reader");
var bytesRead = Parameter(typeof(long).MakeByRefType(), "bytesRead");
var input = Parameter(typeof(ReadOnlySequence<byte>).MakeByRefType(), "input");
var createReader = Assign(reader,
New(typeof(SequenceReader<byte>).GetConstructor(new[] { typeof(ReadOnlySequence<byte>) }), input));
var returnTarget = Label(typeof(bool));
var returnLabel = Label(returnTarget, Constant(default(bool)));
var returnFalse =
Block(
Assign(bytesRead,
Property(reader,
typeof(SequenceReader<byte>).GetProperty(nameof(SequenceReader<byte>.Consumed)))),
Block(Return(returnTarget, Constant(false), typeof(bool)), returnLabel));
var returnTrue =
Block(
Assign(bytesRead,
Property(reader,
typeof(SequenceReader<byte>).GetProperty(nameof(SequenceReader<byte>.Consumed)))),
Block(Return(returnTarget, Constant(true), typeof(bool)), returnLabel));
var valueWord = Parameter(typeof(Word), "value");
var wordValueVar = Variable(typeof(string), "wordValue");
Serializer.Setup(Lambda<DeserializerDlg<Word>>(
Block(new[] { reader, wordValueVar },
createReader,
IfThen(
NotEqual(Call(_tryRead.MakeGenericMethod(typeof(string)), reader, wordValueVar), Constant(true)),
returnFalse),
Assign(Property(valueWord, nameof(Word.Value)), wordValueVar),
returnTrue), input, valueWord, bytesRead)
.CompileFast());
var valueSimple = Parameter(typeof(Simple), "value");
var identifierVar = Variable(typeof(int), "identifier");
var contentVar = Variable(typeof(Word[]), "content");
var contentLenVar = Variable(typeof(int), "contentLength");
Serializer.Setup(Lambda<DeserializerDlg<Simple>>(
Block(new[] { reader, identifierVar, contentVar, contentLenVar },
createReader,
IfThen(
NotEqual(Call(_tryRead.MakeGenericMethod(typeof(int)), reader, identifierVar), Constant(true)),
returnFalse),
IfThen(
NotEqual(Call(_tryRead.MakeGenericMethod(typeof(int)), reader, contentLenVar), Constant(true)),
returnFalse),
IfThen(
NotEqual(Call(_tryDeserialize.MakeGenericMethod(typeof(Word)), reader, contentLenVar, contentVar), Constant(true)),
returnFalse),
Assign(Property(valueSimple, nameof(Simple.Identifier)), identifierVar),
Assign(Property(valueSimple, nameof(Simple.Sentence)), contentVar),
returnTrue), input, valueSimple, bytesRead)
.CompileFast());
}
[Test]
public void TryDeserialize_ShouldParseSimple()
{
var expected = new Simple { Identifier = 150, Sentence = new[] { new Word { Value = "hello" }, new Word { Value = "there" } } };
Memory<byte> buffer = new byte[20];
BinaryPrimitives.WriteInt32BigEndian(buffer.Span, expected.Identifier);
buffer.Span.Slice(4)[0] = 2;
BinaryPrimitives.WriteInt16BigEndian(buffer.Span.Slice(5), 5);
Encoding.UTF8.GetBytes(expected.Sentence[0].Value, buffer.Span.Slice(7));
BinaryPrimitives.WriteInt16BigEndian(buffer.Span.Slice(13), 5);
Encoding.UTF8.GetBytes(expected.Sentence[01].Value, buffer.Span.Slice(15));
var deserialized = new Simple();
var input = new ReadOnlySequence<byte>(buffer);
Assert.True(Serializer.TryDeserialize(ref input, deserialized, out var bytesRead));
Assert.AreEqual(buffer.Length, bytesRead);
Assert.True(expected.Equals(deserialized));
}
}
#pragma warning disable CS0659 // Le type se substitue à Object.Equals(object o) mais pas à Object.GetHashCode()
internal class Simple
#pragma warning restore CS0659 // Le type se substitue à Object.Equals(object o) mais pas à Object.GetHashCode()
{
public int Identifier { get; set; }
public Word[] Sentence { get; set; }
#pragma warning disable 659
public override bool Equals(object obj)
#pragma warning restore 659
{
return obj is Simple value
&& value.Identifier == Identifier
&& value.Sentence.SequenceEqual(Sentence);
}
}
internal class Word
{
public string Value { get; set; }
}
internal struct VarShort
{
public short Value { get; set; }
public VarShort(short value) => Value = value;
}
internal static class Serializer
{
public static bool TryDeserialize<T>(ref ReadOnlySequence<byte> input, T value, out long byteRead) =>
SerializerStorage<T>.TryDeserialize(ref input, value, out byteRead);
public static bool TryDeserializeValues<T>(ref SequenceReader<byte> input, int length, out T[] values) where T : new()
{
if (length == 0)
{
values = Array.Empty<T>();
return true;
}
values = new T[length];
for (var i = 0; i < length; i++)
{
var current = new T();
var b = input.GetRemainingSequence();
if (!TryDeserialize(ref b, current, out var bytesRead))
{
values = Array.Empty<T>();
return false;
}
values[i] = current;
input.Advance(bytesRead);
}
return true;
}
public static void Setup<T>(DeserializerDlg<T> des) => SerializerStorage<T>.TryDeserialize = des;
private static class SerializerStorage<T>
{
public static DeserializerDlg<T> TryDeserialize { get; set; }
}
}
internal static class ReaderExtensions
{
public static bool TryReadValue<T>(this ref SequenceReader<byte> reader, out T value) =>
ReaderStorage<T>.TryRead(ref reader, out value);
static ReaderExtensions()
{
ReaderStorage<int>.TryRead = (ref SequenceReader<byte> reader, out int value) =>
reader.TryReadBigEndian(out value);
ReaderStorage<string>.TryRead = (ref SequenceReader<byte> reader, out string value) =>
{
value = default;
if (!reader.TryReadBigEndian(out short len)) return false;
var strLen = unchecked((ushort)len);
if (reader.Remaining < strLen) return false;
var strSequence = reader.Sequence.Slice(reader.Position, strLen);
value = strSequence.AsString();
reader.Advance(strLen);
return true;
};
ReaderStorage<VarShort>.TryRead = (ref SequenceReader<byte> reader, out VarShort value) =>
{
var result = 0;
for (var offset = 0; offset < 16; offset += 7)
{
if (!reader.TryRead(out var readByte))
{
value = default;
return false;
}
var hasNext = (readByte & 128) == 128;
if (offset > 0) result += (readByte & sbyte.MaxValue) << offset;
else result += (readByte & sbyte.MaxValue);
if (result > short.MaxValue) result -= 65536;
if (!hasNext) break;
}
value = new VarShort((short)result);
return true;
};
}
private static class ReaderStorage<T>
{
public delegate bool TryReadValue(ref SequenceReader<byte> reader, out T value);
public static TryReadValue TryRead { get; set; }
}
}
internal static class SequenceExtensions
{
public static string AsString(this ref ReadOnlySequence<byte> buffer, Encoding useEncoding = default)
{
var encoding = useEncoding ?? Encoding.UTF8;
if (buffer.IsSingleSegment)
return encoding.GetString(buffer.First.Span);
return string.Create((int)buffer.Length, buffer, (span, sequence) =>
{
foreach (var segment in sequence)
{
encoding.GetChars(segment.Span, span);
span = span.Slice(segment.Length);
}
});
}
public static ReadOnlySequence<byte> GetRemainingSequence(this ref SequenceReader<byte> r) =>
r.Sequence.Slice(r.Position);
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment