diff --git a/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs b/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs new file mode 100644 index 0000000..f2e63ad --- /dev/null +++ b/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace PbfLite.Tests; + +public class PbfBlockReaderStreamReaderRoundTripTests +{ + [Fact] + public void BlockWriterAndStreamReader_RoundtripWorksCorrectly() + { + var buffer = new byte[1024]; + var writer = PbfBlockWriter.Create(buffer); + + // Write various values + writer.WriteString("Hello World"); + writer.WriteBoolean(true); + writer.WriteSignedInt(-42); + writer.WriteInt(12345); + writer.WriteUint(67890u); + writer.WriteSignedLong(-9876543210L); + writer.WriteLong(1234567890123456789L); + writer.WriteULong(18446744073709551614ul); + writer.WriteSingle(3.14f); + writer.WriteDouble(2.718281828); + + // Create a stream reader from the written data + using (var stream = new MemoryStream(writer.Block.ToArray())) + { + var reader = new PbfStreamReader(stream); + + // Read and verify all values + Assert.Equal("Hello World", reader.ReadString()); + Assert.True(reader.ReadBoolean()); + Assert.Equal(-42, reader.ReadSignedInt()); + Assert.Equal(12345, reader.ReadInt()); + Assert.Equal(67890u, reader.ReadUint()); + Assert.Equal(-9876543210L, reader.ReadSignedLong()); + Assert.Equal(1234567890123456789L, reader.ReadLong()); + Assert.Equal(18446744073709551614ul, reader.ReadULong()); + Assert.Equal(3.14f, reader.ReadSingle()); + Assert.Equal(2.718281828, reader.ReadDouble()); + } + } + + [Fact] + public void BlockReaderAndBlockWriter_ProduceIdenticalResults() + { + var buffer = new byte[1024]; + var writer = PbfBlockWriter.Create(buffer); + + // Write test data + writer.WriteSignedInt(-12345); + writer.WriteString("Test String"); + writer.WriteBoolean(false); + + // Read with BlockReader + var blockReader = PbfBlockReader.Create(writer.Block); + var blockSignedInt = blockReader.ReadSignedInt(); + var blockString = blockReader.ReadString(); + var blockBool = blockReader.ReadBoolean(); + + // Read with StreamReader + using (var stream = new MemoryStream(writer.Block.ToArray())) + { + var streamReader = new PbfStreamReader(stream); + var streamSignedInt = streamReader.ReadSignedInt(); + var streamString = streamReader.ReadString(); + var streamBool = streamReader.ReadBoolean(); + + // Both readers should produce identical results + Assert.Equal(blockSignedInt, streamSignedInt); + Assert.Equal(blockString, streamString); + Assert.Equal(blockBool, streamBool); + } + } + + [Fact] + public void StreamReader_HandlesFieldHeaders_SameAsBlockReader() + { + var buffer = new byte[1024]; + var writer = PbfBlockWriter.Create(buffer); + + // Write field headers and values + writer.WriteFieldHeader(1, WireType.String); // Field 1, string wire type + writer.WriteString("Value1"); + writer.WriteFieldHeader(2, WireType.VarInt); // Field 2, varint wire type + writer.WriteInt(42); + + // Read with BlockReader + var blockReader = PbfBlockReader.Create(writer.Block); + var header1 = blockReader.ReadFieldHeader(); + var val1 = blockReader.ReadString(); + var header2 = blockReader.ReadFieldHeader(); + var val2 = blockReader.ReadInt(); + + // Read with StreamReader + using (var stream = new MemoryStream(writer.Block.ToArray())) + { + var streamReader = new PbfStreamReader(stream); + var sHeader1 = streamReader.ReadFieldHeader(); + var sVal1 = streamReader.ReadString(); + var sHeader2 = streamReader.ReadFieldHeader(); + var sVal2 = streamReader.ReadInt(); + + // Verify field headers match + Assert.Equal(header1.fieldNumber, sHeader1.fieldNumber); + Assert.Equal(header1.wireType, sHeader1.wireType); + Assert.Equal(header2.fieldNumber, sHeader2.fieldNumber); + Assert.Equal(header2.wireType, sHeader2.wireType); + + // Verify values match + Assert.Equal(val1, sVal1); + Assert.Equal(val2, sVal2); + } + } + + [Fact] + public void StreamReader_WithCollections_MatchesBlockReader() + { + var buffer = new byte[1024]; + var writer = PbfBlockWriter.Create(buffer); + + // Write a collection + uint[] testData = { 10, 20, 30, 40, 50 }; + writer.WriteUIntCollection(testData); + + // Read with BlockReader + var blockReader = PbfBlockReader.Create(writer.Block); + var blockItems = blockReader.ReadUIntCollection(WireType.String, new uint[testData.Length]); + + // Read with StreamReader + using (var stream = new MemoryStream(writer.Block.ToArray())) + { + var streamReader = new PbfStreamReader(stream); + var streamItems = new List(); + streamReader.ReadUIntCollection(WireType.String, streamItems); + + // Verify both readers get the same collection data + Assert.Equal(blockItems.Length, streamItems.Count); + for (int i = 0; i < testData.Length; i++) + { + Assert.Equal(blockItems[i], streamItems[i]); + } + } + } + + [Fact] + public void StreamReader_ReadsMinimalBytesPerPrimitive() + { + // Create a buffer with data for a varint 5, followed by junk + byte[] data = { 0x05, 0xFF, 0xFF, 0xFF }; + + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var value = reader.ReadVarInt32(); + + // Should have read exactly 1 byte (just the varint 5) + Assert.Equal(5u, value); + Assert.Equal(1, stream.Position); + } + } + + [Fact] + public void StreamReader_ReadsExactFixedSize() + { + byte[] data = { 0x01, 0x02, 0x03, 0x04, 0xFF, 0xFF, 0xFF, 0xFF }; + + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var value = reader.ReadFixed32(); + + // Should have read exactly 4 bytes + Assert.Equal(0x04030201U, value); + Assert.Equal(4, stream.Position); + } + } + + [Fact] + public void StreamReader_SkipsFieldsCorrectly() + { + var buffer = new byte[1024]; + var writer = PbfBlockWriter.Create(buffer); + + writer.WriteFieldHeader(1, WireType.VarInt); + writer.WriteInt(12345); + writer.WriteFieldHeader(2, WireType.String); + writer.WriteString("Skip me"); + writer.WriteFieldHeader(3, WireType.VarInt); + writer.WriteInt(67890); + + using (var stream = new MemoryStream(writer.Block.ToArray())) + { + var reader = new PbfStreamReader(stream); + + var header1 = reader.ReadFieldHeader(); + var value1 = reader.ReadInt(); + + var header2 = reader.ReadFieldHeader(); + reader.SkipField(header2.wireType); // Skip field 2 + + var header3 = reader.ReadFieldHeader(); + var value3 = reader.ReadInt(); + + Assert.Equal(1, header1.fieldNumber); + Assert.Equal(12345, value1); + Assert.Equal(2, header2.fieldNumber); + Assert.Equal(3, header3.fieldNumber); + Assert.Equal(67890, value3); + } + } +} diff --git a/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs b/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs index 0fb835f..ce5f461 100644 --- a/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs +++ b/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs @@ -1,365 +1,86 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -using Xunit; namespace PbfLite.Tests; public partial class PbfBlockReaderTests { - public class CollectionsToBuffer : Collections - { - protected override CollectionReaderDelegate UIntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadUIntCollection(wireType, new uint[itemCount]); - protected override CollectionReaderDelegate ULongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadULongCollection(wireType, new ulong[itemCount]); - protected override CollectionReaderDelegate IntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadIntCollection(wireType, new int[itemCount]); - protected override CollectionReaderDelegate SignedIntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadSignedIntCollection(wireType, new int[itemCount]); - protected override CollectionReaderDelegate LongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadLongCollection(wireType, new long[itemCount]); - protected override CollectionReaderDelegate SignedLongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadSignedLongCollection(wireType, new long[itemCount]); - protected override CollectionReaderDelegate BooleanCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadBooleanCollection(wireType, new bool[itemCount]); - protected override CollectionReaderDelegate SingleCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadSingleCollection(wireType, new float[itemCount]); - protected override CollectionReaderDelegate DoubleCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => reader.ReadDoubleCollection(wireType, new double[itemCount]); - } + private delegate void ReadCollectionDelegate(PbfBlockReader reader, WireType wireType, T collection); - public class CollectionsToList : Collections + public class CollectionsToBuffer : PbfReaderCollectionsTests { - protected override CollectionReaderDelegate UIntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => - { - var list = new List(itemCount); - reader.ReadUIntCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; - - protected override CollectionReaderDelegate ULongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => - { - var list = new List(itemCount); - reader.ReadULongCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; - - protected override CollectionReaderDelegate IntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => - { - var list = new List(itemCount); - reader.ReadIntCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; - - protected override CollectionReaderDelegate SignedIntCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => - { - var list = new List(itemCount); - reader.ReadSignedIntCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; - - protected override CollectionReaderDelegate LongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => - { - var list = new List(itemCount); - reader.ReadLongCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; - - protected override CollectionReaderDelegate SignedLongCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => - { - var list = new List(itemCount); - reader.ReadSignedLongCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; - - protected override CollectionReaderDelegate BooleanCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => - { - var list = new List(itemCount); - reader.ReadBooleanCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; - - protected override CollectionReaderDelegate SingleCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => - { - var list = new List(itemCount); - reader.ReadSingleCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; - - protected override CollectionReaderDelegate DoubleCollectionReader => (ref PbfBlockReader reader, WireType wireType, int itemCount) => - { - var list = new List(itemCount); - reader.ReadDoubleCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; - } - - public abstract class Collections - { - protected delegate ReadOnlySpan CollectionReaderDelegate(ref PbfBlockReader reader, WireType wireType, int itemCount); - - protected abstract CollectionReaderDelegate UIntCollectionReader { get; } - protected abstract CollectionReaderDelegate ULongCollectionReader { get; } - protected abstract CollectionReaderDelegate IntCollectionReader { get; } - protected abstract CollectionReaderDelegate SignedIntCollectionReader { get; } - protected abstract CollectionReaderDelegate LongCollectionReader { get; } - protected abstract CollectionReaderDelegate SignedLongCollectionReader { get; } - protected abstract CollectionReaderDelegate BooleanCollectionReader { get; } - protected abstract CollectionReaderDelegate SingleCollectionReader { get; } - protected abstract CollectionReaderDelegate DoubleCollectionReader { get; } - - [Theory] - [InlineData(new byte[] { 0x00 }, new uint[] { 0 })] - [InlineData(new byte[] { 0x01 }, new uint[] { 1 })] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, new uint[] { 268435456 })] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new uint[] { 4294967295 })] - - public void ReadUIntCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, uint[] expectedItems) - { - var buffer = new uint[1]; - var reader = PbfBlockReader.Create(data); - - var items = UIntCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new uint[] { 0 })] - [InlineData(new byte[] { 0x01, 0x01 }, new uint[] { 1 })] - [InlineData(new byte[] { 0x05, 0x80, 0x80, 0x80, 0x80, 0x01 }, new uint[] { 268435456 })] - [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new uint[] { 4294967295 })] - [InlineData(new byte[] { 0x14, 0x00, 0x80, 0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new uint[] { 0, 128, 16384, 2097152, 268435456, 4294967295 })] - public void ReadUIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, uint[] expectedItems) - { - var buffer = new uint[expectedItems.Length]; - var reader = PbfBlockReader.Create(data); - - var items = UIntCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x00 }, new ulong[] { 0 })] - [InlineData(new byte[] { 0x01 }, new ulong[] { 1 })] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new ulong[] { 18446744073709551615UL })] - public void ReadULongCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, ulong[] expectedItems) + private static ReadOnlySpan ReadCollection(byte[] data, WireType wireType, int itemCount, ReadCollectionDelegate readAction) { + var buffer = new T[itemCount]; var reader = PbfBlockReader.Create(data); - - var items = ULongCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + readAction(reader, wireType, buffer); + return buffer; } - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new ulong[] { 0 })] - [InlineData(new byte[] { 0x01, 0x01 }, new ulong[] { 1 })] - [InlineData(new byte[] { 0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new ulong[] { 18446744073709551615UL })] - [InlineData(new byte[] { 0x0C, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new ulong[] { 0, 1, 18446744073709551615UL })] - public void ReadULongCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, ulong[] expectedItems) - { - var reader = PbfBlockReader.Create(data); + protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadUIntCollection(wireType, buffer)); - var items = ULongCollectionReader(ref reader, WireType.String, expectedItems.Length); + protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadULongCollection(wireType, buffer)); - SpanAssert.Equal(expectedItems.AsSpan(), items); - } + protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadIntCollection(wireType, buffer)); + protected override ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadSignedIntCollection(wireType, buffer)); - [Theory] - [InlineData(new byte[] { 0x00 }, new int[] { 0 })] - [InlineData(new byte[] { 0x01 }, new int[] { 1 })] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -1 })] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x07 }, new int[] { 2147483647 })] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -2147483648 })] - public void ReadIntCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) - { - var reader = PbfBlockReader.Create(data); + protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadLongCollection(wireType, buffer)); - var items = IntCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); + protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadSignedLongCollection(wireType, buffer)); - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new int[] { 0 })] - [InlineData(new byte[] { 0x01, 0x01 }, new int[] { 1 })] - [InlineData(new byte[] { 0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -1 })] - [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x07 }, new int[] { 2147483647 })] - [InlineData(new byte[] { 0x0A, 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -2147483648 })] - [InlineData(new byte[] { 0x19, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { 0, 1, -1, 2147483647, -2147483648 })] - public void ReadIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) - { - var reader = PbfBlockReader.Create(data); + protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadBooleanCollection(wireType, buffer)); - var items = IntCollectionReader(ref reader, WireType.String, expectedItems.Length); + protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadSingleCollection(wireType, buffer)); - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x00 }, new int[] { 0 })] - [InlineData(new byte[] { 0x01 }, new int[] { -1 })] - [InlineData(new byte[] { 0x02 }, new int[] { 1 })] - [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { 2147483647 })] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -2147483648 })] - public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) - { - var reader = PbfBlockReader.Create(data); - - var items = SignedIntCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new int[] { 0 })] - [InlineData(new byte[] { 0x01, 0x01 }, new int[] { -1 })] - [InlineData(new byte[] { 0x01, 0x02 }, new int[] { 1 })] - [InlineData(new byte[] { 0x05, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { 2147483647 })] - [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -2147483648 })] - [InlineData(new byte[] { 0x0D, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { 0, -1, 1, 2147483647, -2147483648 })] - public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) - { - var reader = PbfBlockReader.Create(data); - - var items = SignedIntCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x00 }, new long[] { 0 })] - [InlineData(new byte[] { 0x01 }, new long[] { 1 })] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { -1 })] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, new long[] { 9223372036854775807 })] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, new long[] { -9223372036854775808 })] - public void ReadLongCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) - { - var reader = PbfBlockReader.Create(data); - - var items = LongCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new long[] { 0 })] - [InlineData(new byte[] { 0x01, 0x01 }, new long[] { 1 })] - [InlineData(new byte[] { 0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { -1 })] - [InlineData(new byte[] { 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, new long[] { 9223372036854775807 })] - [InlineData(new byte[] { 0x0A, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, new long[] { -9223372036854775808 })] - [InlineData(new byte[] { 0x1F, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, new long[] { 0, 1, -1, 9223372036854775807, -9223372036854775808 })] - public void ReadLongCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) - { - var reader = PbfBlockReader.Create(data); - - var items = LongCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x00 }, new long[] { 0 })] - [InlineData(new byte[] { 0x01 }, new long[] { -1 })] - [InlineData(new byte[] { 0x02 }, new long[] { 1 })] - [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { 9223372036854775807 })] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { -9223372036854775808 })] - public void ReadSignedLongCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) - { - var reader = PbfBlockReader.Create(data); - - var items = SignedLongCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new long[] { 0 })] - [InlineData(new byte[] { 0x01, 0x01 }, new long[] { -1 })] - [InlineData(new byte[] { 0x01, 0x02 }, new long[] { 1 })] - [InlineData(new byte[] { 0x17, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { 0, -1, 1, 9223372036854775807, -9223372036854775808 })] - public void ReadSignedLongCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) - { - var reader = PbfBlockReader.Create(data); - - var items = SignedLongCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x00 }, new bool[] { false })] - [InlineData(new byte[] { 0x01 }, new bool[] { true })] - public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, bool[] expectedItems) - { - var reader = PbfBlockReader.Create(data); - - var items = BooleanCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } - - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new bool[] { false })] - [InlineData(new byte[] { 0x01, 0x01 }, new bool[] { true })] - [InlineData(new byte[] { 0x02, 0x00, 0x01 }, new bool[] { false, true })] - public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, bool[] expectedItems) - { - var reader = PbfBlockReader.Create(data); - - var items = BooleanCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); - } + protected override ReadOnlySpan ReadDoubleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadDoubleCollection(wireType, buffer)); + } - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0 })] - [InlineData(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 3.14f })] - [InlineData(new byte[] { 0xF7, 0xCC, 0x12, 0x39 }, new float[] { 0.00014f })] - [InlineData(new byte[] { 0x66, 0x80, 0x3B, 0x46 }, new float[] { 12000.1f })] - public void ReadSingleCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, float[] expectedItems) + public class CollectionsToList : PbfReaderCollectionsTests + { + private static ReadOnlySpan ReadCollection(byte[] data, WireType wireType, int itemCount, ReadCollectionDelegate> readAction) { var reader = PbfBlockReader.Create(data); - - var items = SingleCollectionReader(ref reader, WireType.Fixed32, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + var list = new List(itemCount); + readAction(reader, wireType, list); + return CollectionsMarshal.AsSpan(list); } - [Theory] - [InlineData(new byte[] { 0x04, 0x00, 0x00, 0x00, 0x00 }, new float[] { 0 })] - [InlineData(new byte[] { 0x04, 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 3.14f })] - [InlineData(new byte[] { 0x10, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xF5, 0x48, 0x40, 0xF7, 0xCC, 0x12, 0x39, 0x66, 0x80, 0x3B, 0x46 }, new float[] { 0, 3.14f, 0.00014f, 12000.1f })] - public void ReadSingleCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, float[] expectedItems) - { - var reader = PbfBlockReader.Create(data); + protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadUIntCollection(wireType, list)); - var items = SingleCollectionReader(ref reader, WireType.String, expectedItems.Length); + protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadULongCollection(wireType, list)); - SpanAssert.Equal(expectedItems.AsSpan(), items); - } + protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadIntCollection(wireType, list)); - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0 })] - [InlineData(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 3.14 })] - [InlineData(new byte[] { 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F }, new double[] { 0.00014 })] - [InlineData(new byte[] { 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 }, new double[] { 12000.1 })] - public void ReadDoubleCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, double[] expectedItems) - { - var reader = PbfBlockReader.Create(data); + protected override ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadSignedIntCollection(wireType, list)); - var items = DoubleCollectionReader(ref reader, WireType.Fixed64, expectedItems.Length); + protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadLongCollection(wireType, list)); - SpanAssert.Equal(expectedItems.AsSpan(), items); - } + protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadSignedLongCollection(wireType, list)); - [Theory] - [InlineData(new byte[] { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0 })] - [InlineData(new byte[] { 0x08, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 3.14 })] - [InlineData(new byte[] { 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40, 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F, 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 }, new double[] { 0, 3.14, 0.00014, 12000.1 })] - public void ReadDoubleCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, double[] expectedItems) - { - var reader = PbfBlockReader.Create(data); + protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadBooleanCollection(wireType, list)); - var items = DoubleCollectionReader(ref reader, WireType.String, expectedItems.Length); + protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadSingleCollection(wireType, list)); - SpanAssert.Equal(expectedItems.AsSpan(), items); - } + protected override ReadOnlySpan ReadDoubleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadDoubleCollection(wireType, list)); } } \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfBlockReaderTests.Primitives.cs b/src/PbfLite.Tests/PbfBlockReaderTests.Primitives.cs index 049997b..90ecd37 100644 --- a/src/PbfLite.Tests/PbfBlockReaderTests.Primitives.cs +++ b/src/PbfLite.Tests/PbfBlockReaderTests.Primitives.cs @@ -1,91 +1,39 @@ -using Xunit; +using System; namespace PbfLite.Tests; -public partial class PbfBlockReaderTests +public partial class PbfBlockReaderTests : PbfReaderTests { - public class Primitives + public class Primitives : PbfReaderPrimitivesTests { - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0)] - [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 1)] - [InlineData(new byte[] { 0xFF, 0x00, 0x00, 0x00 }, 255)] - [InlineData(new byte[] { 0x00, 0x01, 0x00, 0x00 }, 256)] - [InlineData(new byte[] { 0x00, 0x00, 0x01, 0x00 }, 65536)] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x01 }, 16777216)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, 4294967295)] - public void ReadFixed32_ReadsNumbers(byte[] data, uint expectedNumber) + public override uint ReadFixed32(byte[] data) { var reader = PbfBlockReader.Create(data); - - var number = reader.ReadFixed32(); - - Assert.Equal(expectedNumber, number); + return reader.ReadFixed32(); } - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0)] - [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 1)] - [InlineData(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 255)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }, 4294967295)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, 18446744073709551615UL)] - public void ReadFixed64_ReadsNumbers(byte[] data, ulong expectedNumber) + public override ulong ReadFixed64(byte[] data) { var reader = PbfBlockReader.Create(data); - - var number = reader.ReadFixed64(); - - Assert.Equal(expectedNumber, number); + return reader.ReadFixed64(); } - [Fact] - public void ReadLengthPrefixedBytes_ReadsData() + public override uint ReadVarInt32(byte[] data) { - var reader = PbfBlockReader.Create([0x03, 0x41, 0x42, 0x43]); - - var data = reader.ReadLengthPrefixedBytes(); - - Assert.Equal([0x41, 0x42, 0x43], data.ToArray()); + var reader = PbfBlockReader.Create(data); + return reader.ReadVarInt32(); } - [Theory] - [InlineData(new byte[] { 0x00 }, 0)] - [InlineData(new byte[] { 0x01 }, 1)] - [InlineData(new byte[] { 0x7F }, 127)] - [InlineData(new byte[] { 0x80, 0x01 }, 128)] - [InlineData(new byte[] { 0x80, 0x80, 0x01 }, 16384)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x01 }, 2097152)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, 268435456)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, 4294967295)] - public void ReadVarint32_ReadsNumbers(byte[] data, uint expectedNumber) + public override ulong ReadVarInt64(byte[] data) { var reader = PbfBlockReader.Create(data); - - var number = reader.ReadVarInt32(); - - Assert.Equal(expectedNumber, number); + return reader.ReadVarInt64(); } - [Theory] - [InlineData(new byte[] { 0x00 }, 0)] - [InlineData(new byte[] { 0x01 }, 1)] - [InlineData(new byte[] { 0x7F }, 127)] - [InlineData(new byte[] { 0x80, 0x01 }, 128)] - [InlineData(new byte[] { 0x80, 0x80, 0x01 }, 16384)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x01 }, 2097152)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, 268435456)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, 34359738368UL)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, 4398046511104UL)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, 562949953421312UL)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, 72057594037927936UL)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, 18446744073709551615UL)] - public void ReadVarint64_ReadsNumbers(byte[] data, ulong expectedNumber) + public override ReadOnlySpan ReadLengthPrefixedBytes(byte[] data) { var reader = PbfBlockReader.Create(data); - - var number = reader.ReadVarInt64(); - - Assert.Equal(expectedNumber, number); + return reader.ReadLengthPrefixedBytes(); } } -} \ No newline at end of file +} diff --git a/src/PbfLite.Tests/PbfBlockReaderTests.SystemTypes.cs b/src/PbfLite.Tests/PbfBlockReaderTests.SystemTypes.cs index b50d5b2..3edbba8 100644 --- a/src/PbfLite.Tests/PbfBlockReaderTests.SystemTypes.cs +++ b/src/PbfLite.Tests/PbfBlockReaderTests.SystemTypes.cs @@ -4,118 +4,66 @@ namespace PbfLite.Tests; public partial class PbfBlockReaderTests { - public class SystemTypes + public class SystemTypes : PbfReaderSystemTypesTests { - [Theory] - [InlineData(new byte[] { 0x0C, 0x45, 0x6E, 0x67, 0x6C, 0x69, 0x73, 0x68, 0x20, 0x74, 0x65, 0x78, 0x74 }, "English text")] - [InlineData(new byte[] { 0x0C, 0xC4, 0x8C, 0x65, 0x73, 0x6B, 0xC3, 0xBD, 0x20, 0x74, 0x65, 0x78, 0x74 }, "Český text")] - public void ReadString_ReadsUtf8EncodedString(byte[] data, string expectedText) + public override string ReadString(byte[] data) { var reader = PbfBlockReader.Create(data); - - var text = reader.ReadString(); - - Assert.Equal(expectedText, text); + return reader.ReadString(); } - [Theory] - [InlineData(new byte[] { 0x00 }, false)] - [InlineData(new byte[] { 0x01 }, true)] - public void ReadBoolean_ReadsValuesEncodedAsVarint(byte[] data, bool expectedValue) + public override bool ReadBoolean(byte[] data) { var reader = PbfBlockReader.Create(data); - - var value = reader.ReadBoolean(); - - Assert.Equal(expectedValue, value); + return reader.ReadBoolean(); } - [Theory] - [InlineData(new byte[] { 0x00 }, 0)] - [InlineData(new byte[] { 0x01 }, -1)] - [InlineData(new byte[] { 0x02 }, 1)] - [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0x0F }, 2147483647)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, -2147483648)] - public void ReadSignedInt_ReadsZiggedVarintValues(byte[] data, int expectedNumber) + public override int ReadSignedInt(byte[] data) { var reader = PbfBlockReader.Create(data); - - var number = reader.ReadSignedInt(); - - Assert.Equal(expectedNumber, number); + return reader.ReadSignedInt(); } - [Theory] - [InlineData(new byte[] { 0x00 }, 0)] - [InlineData(new byte[] { 0x01 }, 1)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, -1)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x07 }, 2147483647)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, -2147483648)] - public void ReadInt_ReadsVarintValues(byte[] data, int expectedNumber) + public override int ReadInt(byte[] data) { var reader = PbfBlockReader.Create(data); - - var number = reader.ReadInt(); - - Assert.Equal(expectedNumber, number); + return reader.ReadInt(); } - [Theory] - [InlineData(new byte[] { 0x00 }, 0)] - [InlineData(new byte[] { 0x01 }, -1)] - [InlineData(new byte[] { 0x02 }, 1)] - [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, 9223372036854775807)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, -9223372036854775808)] - public void ReadSignedLong_ReadsZiggedVarintValues(byte[] data, long expectedNumber) + public override uint ReadUint(byte[] data) { var reader = PbfBlockReader.Create(data); - - var number = reader.ReadSignedLong(); - - Assert.Equal(expectedNumber, number); + return reader.ReadUint(); } - [Theory] - [InlineData(new byte[] { 0x00 }, 0)] - [InlineData(new byte[] { 0x01 }, 1)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, -1)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, 9223372036854775807)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, -9223372036854775808)] - public void ReadLong_ReadsVarintValues(byte[] data, long expectedNumber) + public override long ReadSignedLong(byte[] data) { var reader = PbfBlockReader.Create(data); - - var number = reader.ReadLong(); - - Assert.Equal(expectedNumber, number); + return reader.ReadSignedLong(); } - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0)] - [InlineData(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }, 3.14f)] - [InlineData(new byte[] { 0xF7, 0xCC, 0x12, 0x39 }, 0.00014f)] - [InlineData(new byte[] { 0x66, 0x80, 0x3B, 0x46 }, 12000.1f)] - public void ReadSingle_ReadsValues(byte[] data, float expectedNumber) + public override long ReadLong(byte[] data) { var reader = PbfBlockReader.Create(data); - - var number = reader.ReadSingle(); - - Assert.Equal(expectedNumber, number); + return reader.ReadLong(); } - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0)] - [InlineData(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, 3.14)] - [InlineData(new byte[] { 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F }, 0.00014)] - [InlineData(new byte[] { 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 }, 12000.1)] - public void ReadDouble_ReadsValues(byte[] data, double expectedNumber) + public override ulong ReadULong(byte[] data) { var reader = PbfBlockReader.Create(data); + return reader.ReadULong(); + } - var number = reader.ReadDouble(); + public override float ReadSingle(byte[] data) + { + var reader = PbfBlockReader.Create(data); + return reader.ReadSingle(); + } - Assert.Equal(expectedNumber, number); + public override double ReadDouble(byte[] data) + { + var reader = PbfBlockReader.Create(data); + return reader.ReadDouble(); } } } \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfBlockReaderTests.cs b/src/PbfLite.Tests/PbfBlockReaderTests.cs index 71f76bd..2702751 100644 --- a/src/PbfLite.Tests/PbfBlockReaderTests.cs +++ b/src/PbfLite.Tests/PbfBlockReaderTests.cs @@ -2,44 +2,12 @@ namespace PbfLite.Tests; -public partial class PbfBlockReaderTests +public partial class PbfBlockReaderTests : PbfReaderTests { - [Theory] - [InlineData(new byte[] { 0x08 }, 1)] - [InlineData(new byte[] { 0x78 }, 15)] - [InlineData(new byte[] { 0x80, 0x01 }, 16)] - public void ReadFieldHeader_ReadsFieldNumbers(byte[] data, int fieldNumber) + public override (int fieldNumber, WireType wireType) ReadFieldHeader(byte[] data) { var reader = PbfBlockReader.Create(data); - - var header = reader.ReadFieldHeader(); - - Assert.Equal(fieldNumber, header.fieldNumber); - } - - [Theory] - [InlineData(new byte[] { 0x08 }, WireType.VarInt)] - [InlineData(new byte[] { 0x09 }, WireType.Fixed64)] - [InlineData(new byte[] { 0x0A }, WireType.String)] - [InlineData(new byte[] { 0x0D }, WireType.Fixed32)] - public void ReadFieldHeader_ReadsWireTypes(byte[] data, WireType wireType) - { - var reader = PbfBlockReader.Create(data); - - var header = reader.ReadFieldHeader(); - - Assert.Equal(wireType, header.wireType); - } - - [Fact] - public void ReadFieldHeader_ReturnsNoneWhenEndOfBlockIsReached() - { - var reader = PbfBlockReader.Create([]); - - var header = reader.ReadFieldHeader(); - - Assert.Equal(0, header.fieldNumber); - Assert.Equal(WireType.None, header.wireType); + return reader.ReadFieldHeader(); } [Theory] @@ -57,30 +25,4 @@ public void SkipFields_SkipsCorrectNumberOfBytes(byte[] data, WireType wireType, Assert.Equal(expectedPosition, reader.Position); } - - [Theory] - [InlineData(0, 0)] - [InlineData(1, -1)] - [InlineData(2, 1)] - [InlineData(4294967294, 2147483647)] - [InlineData(4294967295, -2147483648)] - public void Zag_Decodes32BitZiggedValues(uint encodedNumber, int expectedNumber) - { - var number = PbfBlockReader.Zag(encodedNumber); - - Assert.Equal(expectedNumber, number); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(1, -1)] - [InlineData(2, 1)] - [InlineData(18446744073709551614UL, 9223372036854775807L)] - [InlineData(18446744073709551615UL, -9223372036854775808L)] - public void Zag_Decodes64BitZiggedValues(ulong encodedNumber, long expectedNumber) - { - var number = PbfBlockReader.Zag(encodedNumber); - - Assert.Equal(expectedNumber, number); - } -} \ No newline at end of file +} diff --git a/src/PbfLite.Tests/PbfEncodingTests.cs b/src/PbfLite.Tests/PbfEncodingTests.cs new file mode 100644 index 0000000..7016953 --- /dev/null +++ b/src/PbfLite.Tests/PbfEncodingTests.cs @@ -0,0 +1,32 @@ +using Xunit; + +namespace PbfLite.Tests; + +public class PbfEncodingTests +{ + [Theory] + [InlineData(0, 0)] + [InlineData(1, -1)] + [InlineData(2, 1)] + [InlineData(4294967294, 2147483647)] + [InlineData(4294967295, -2147483648)] + public void Zag_Decodes32BitZiggedValues(uint encodedNumber, int expectedNumber) + { + var number = PbfEncoding.Zag(encodedNumber); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(1, -1)] + [InlineData(2, 1)] + [InlineData(18446744073709551614UL, 9223372036854775807L)] + [InlineData(18446744073709551615UL, -9223372036854775808L)] + public void Zag_Decodes64BitZiggedValues(ulong encodedNumber, long expectedNumber) + { + var number = PbfEncoding.Zag(encodedNumber); + + Assert.Equal(expectedNumber, number); + } +} \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfReaderCollectionsTests.cs b/src/PbfLite.Tests/PbfReaderCollectionsTests.cs new file mode 100644 index 0000000..771071c --- /dev/null +++ b/src/PbfLite.Tests/PbfReaderCollectionsTests.cs @@ -0,0 +1,240 @@ +using System; +using Xunit; + +namespace PbfLite.Tests; + +public abstract class PbfReaderCollectionsTests +{ + protected abstract ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount); + protected abstract ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount); + protected abstract ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount); + protected abstract ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount); + protected abstract ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount); + protected abstract ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount); + protected abstract ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount); + protected abstract ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount); + protected abstract ReadOnlySpan ReadDoubleCollection(byte[] data, WireType wireType, int itemCount); + + [Theory] + [InlineData(new byte[] { 0x00 }, new uint[] { 0 })] + [InlineData(new byte[] { 0x01 }, new uint[] { 1 })] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, new uint[] { 268435456 })] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new uint[] { 4294967295 })] + + public void ReadUIntCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, uint[] expectedItems) + { + var items = ReadUIntCollection(data, WireType.VarInt, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new uint[] { 0 })] + [InlineData(new byte[] { 0x01, 0x01 }, new uint[] { 1 })] + [InlineData(new byte[] { 0x05, 0x80, 0x80, 0x80, 0x80, 0x01 }, new uint[] { 268435456 })] + [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new uint[] { 4294967295 })] + [InlineData(new byte[] { 0x14, 0x00, 0x80, 0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x01, 0x80, 0x80, 0x80, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new uint[] { 0, 128, 16384, 2097152, 268435456, 4294967295 })] + public void ReadUIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, uint[] expectedItems) + { + var items = ReadUIntCollection(data, WireType.String, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new ulong[] { 0 })] + [InlineData(new byte[] { 0x01 }, new ulong[] { 1 })] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new ulong[] { 18446744073709551615UL })] + public void ReadULongCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, ulong[] expectedItems) + { + var items = ReadULongCollection(data, WireType.VarInt, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new ulong[] { 0 })] + [InlineData(new byte[] { 0x01, 0x01 }, new ulong[] { 1 })] + [InlineData(new byte[] { 0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new ulong[] { 18446744073709551615UL })] + [InlineData(new byte[] { 0x0C, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new ulong[] { 0, 1, 18446744073709551615UL })] + public void ReadULongCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, ulong[] expectedItems) + { + var items = ReadULongCollection(data, WireType.String, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new int[] { 0 })] + [InlineData(new byte[] { 0x01 }, new int[] { 1 })] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -1 })] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x07 }, new int[] { 2147483647 })] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -2147483648 })] + public void ReadIntCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) + { + var items = ReadIntCollection(data, WireType.VarInt, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new int[] { 0 })] + [InlineData(new byte[] { 0x01, 0x01 }, new int[] { 1 })] + [InlineData(new byte[] { 0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -1 })] + [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x07 }, new int[] { 2147483647 })] + [InlineData(new byte[] { 0x0A, 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { -2147483648 })] + [InlineData(new byte[] { 0x19, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new int[] { 0, 1, -1, 2147483647, -2147483648 })] + public void ReadIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) + { + var items = ReadIntCollection(data, WireType.String, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new int[] { 0 })] + [InlineData(new byte[] { 0x01 }, new int[] { -1 })] + [InlineData(new byte[] { 0x02 }, new int[] { 1 })] + [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { 2147483647 })] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -2147483648 })] + public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) + { + var items = ReadSignedIntCollection(data, WireType.VarInt, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new int[] { 0 })] + [InlineData(new byte[] { 0x01, 0x01 }, new int[] { -1 })] + [InlineData(new byte[] { 0x01, 0x02 }, new int[] { 1 })] + [InlineData(new byte[] { 0x05, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { 2147483647 })] + [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -2147483648 })] + [InlineData(new byte[] { 0x0D, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { 0, -1, 1, 2147483647, -2147483648 })] + public void ReadSignedIntCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) + { + var items = ReadSignedIntCollection(data, WireType.String, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new long[] { 0 })] + [InlineData(new byte[] { 0x01 }, new long[] { 1 })] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { -1 })] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, new long[] { 9223372036854775807 })] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, new long[] { -9223372036854775808 })] + public void ReadLongCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) + { + var items = ReadLongCollection(data, WireType.VarInt, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new long[] { 0 })] + [InlineData(new byte[] { 0x01, 0x01 }, new long[] { 1 })] + [InlineData(new byte[] { 0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { -1 })] + [InlineData(new byte[] { 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, new long[] { 9223372036854775807 })] + [InlineData(new byte[] { 0x0A, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, new long[] { -9223372036854775808 })] + [InlineData(new byte[] { 0x1F, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, new long[] { 0, 1, -1, 9223372036854775807, -9223372036854775808 })] + public void ReadLongCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) + { + var items = ReadLongCollection(data, WireType.String, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new long[] { 0 })] + [InlineData(new byte[] { 0x01 }, new long[] { -1 })] + [InlineData(new byte[] { 0x02 }, new long[] { 1 })] + [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { 9223372036854775807 })] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { -9223372036854775808 })] + public void ReadSignedLongCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) + { + var items = ReadSignedLongCollection(data, WireType.VarInt, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new long[] { 0 })] + [InlineData(new byte[] { 0x01, 0x01 }, new long[] { -1 })] + [InlineData(new byte[] { 0x01, 0x02 }, new long[] { 1 })] + [InlineData(new byte[] { 0x17, 0x00, 0x01, 0x02, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, new long[] { 0, -1, 1, 9223372036854775807, -9223372036854775808 })] + public void ReadSignedLongCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) + { + var items = ReadSignedLongCollection(data, WireType.String, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new bool[] { false })] + [InlineData(new byte[] { 0x01 }, new bool[] { true })] + public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, bool[] expectedItems) + { + var items = ReadBooleanCollection(data, WireType.VarInt, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new bool[] { false })] + [InlineData(new byte[] { 0x01, 0x01 }, new bool[] { true })] + [InlineData(new byte[] { 0x02, 0x00, 0x01 }, new bool[] { false, true })] + public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, bool[] expectedItems) + { + var items = ReadBooleanCollection(data, WireType.String, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0 })] + [InlineData(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 3.14f })] + [InlineData(new byte[] { 0xF7, 0xCC, 0x12, 0x39 }, new float[] { 0.00014f })] + [InlineData(new byte[] { 0x66, 0x80, 0x3B, 0x46 }, new float[] { 12000.1f })] + public void ReadSingleCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, float[] expectedItems) + { + var items = ReadSingleCollection(data, WireType.Fixed32, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x04, 0x00, 0x00, 0x00, 0x00 }, new float[] { 0 })] + [InlineData(new byte[] { 0x04, 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 3.14f })] + [InlineData(new byte[] { 0x10, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xF5, 0x48, 0x40, 0xF7, 0xCC, 0x12, 0x39, 0x66, 0x80, 0x3B, 0x46 }, new float[] { 0, 3.14f, 0.00014f, 12000.1f })] + public void ReadSingleCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, float[] expectedItems) + { + var items = ReadSingleCollection(data, WireType.String, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0 })] + [InlineData(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 3.14 })] + [InlineData(new byte[] { 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F }, new double[] { 0.00014 })] + [InlineData(new byte[] { 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 }, new double[] { 12000.1 })] + public void ReadDoubleCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, double[] expectedItems) + { + var items = ReadDoubleCollection(data, WireType.Fixed64, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } + + [Theory] + [InlineData(new byte[] { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0 })] + [InlineData(new byte[] { 0x08, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 3.14 })] + [InlineData(new byte[] { 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40, 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F, 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 }, new double[] { 0, 3.14, 0.00014, 12000.1 })] + public void ReadDoubleCollection_Buffer_ReadsDataSerializedAsLengthPrefixed(byte[] data, double[] expectedItems) + { + var items = ReadDoubleCollection(data, WireType.String, expectedItems.Length); + + SpanAssert.Equal(expectedItems.AsSpan(), items); + } +} diff --git a/src/PbfLite.Tests/PbfReaderPrimitivesTests.cs b/src/PbfLite.Tests/PbfReaderPrimitivesTests.cs new file mode 100644 index 0000000..9ed7181 --- /dev/null +++ b/src/PbfLite.Tests/PbfReaderPrimitivesTests.cs @@ -0,0 +1,89 @@ +using System; +using Xunit; + +namespace PbfLite.Tests; + +public abstract class PbfReaderPrimitivesTests +{ + public abstract uint ReadFixed32(byte[] data); + + public abstract ulong ReadFixed64(byte[] data); + + public abstract uint ReadVarInt32(byte[] data); + + public abstract ulong ReadVarInt64(byte[] data); + + public abstract ReadOnlySpan ReadLengthPrefixedBytes(byte[] data); + + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0)] + [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 1)] + [InlineData(new byte[] { 0xFF, 0x00, 0x00, 0x00 }, 255)] + [InlineData(new byte[] { 0x00, 0x01, 0x00, 0x00 }, 256)] + [InlineData(new byte[] { 0x00, 0x00, 0x01, 0x00 }, 65536)] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x01 }, 16777216)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, 4294967295)] + public void ReadFixed32_ReadsNumbers(byte[] data, uint expectedNumber) + { + var number = ReadFixed32(data); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0)] + [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 1)] + [InlineData(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 255)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }, 4294967295)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, 18446744073709551615UL)] + public void ReadFixed64_ReadsNumbers(byte[] data, ulong expectedNumber) + { + var number = ReadFixed64(data); + + Assert.Equal(expectedNumber, number); + } + + [Fact] + public void ReadLengthPrefixedBytes_ReadsData() + { + var data = ReadLengthPrefixedBytes([0x03, 0x41, 0x42, 0x43]); + + Assert.Equal([0x41, 0x42, 0x43], data.ToArray()); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0)] + [InlineData(new byte[] { 0x01 }, 1)] + [InlineData(new byte[] { 0x7F }, 127)] + [InlineData(new byte[] { 0x80, 0x01 }, 128)] + [InlineData(new byte[] { 0x80, 0x80, 0x01 }, 16384)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x01 }, 2097152)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, 268435456)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, 4294967295)] + public void ReadVarInt32_ReadsNumbers(byte[] data, uint expectedNumber) + { + var number = ReadVarInt32(data); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0)] + [InlineData(new byte[] { 0x01 }, 1)] + [InlineData(new byte[] { 0x7F }, 127)] + [InlineData(new byte[] { 0x80, 0x01 }, 128)] + [InlineData(new byte[] { 0x80, 0x80, 0x01 }, 16384)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x01 }, 2097152)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, 268435456)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, 34359738368UL)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, 4398046511104UL)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, 562949953421312UL)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, 72057594037927936UL)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, 18446744073709551615UL)] + public void ReadVarInt64_ReadsNumbers(byte[] data, ulong expectedNumber) + { + var number = ReadVarInt64(data); + + Assert.Equal(expectedNumber, number); + } +} \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfReaderSystemTypesTests.cs b/src/PbfLite.Tests/PbfReaderSystemTypesTests.cs new file mode 100644 index 0000000..87a19fa --- /dev/null +++ b/src/PbfLite.Tests/PbfReaderSystemTypesTests.cs @@ -0,0 +1,145 @@ +using System; +using Xunit; + +namespace PbfLite.Tests; + +public abstract class PbfReaderSystemTypesTests +{ + public abstract string ReadString(byte[] data); + + public abstract bool ReadBoolean(byte[] data); + + public abstract int ReadSignedInt(byte[] data); + + public abstract int ReadInt(byte[] data); + + public abstract uint ReadUint(byte[] data); + + public abstract long ReadSignedLong(byte[] data); + + public abstract long ReadLong(byte[] data); + + public abstract ulong ReadULong(byte[] data); + + public abstract float ReadSingle(byte[] data); + + public abstract double ReadDouble(byte[] data); + + [Theory] + [InlineData(new byte[] { 0x0C, 0x45, 0x6E, 0x67, 0x6C, 0x69, 0x73, 0x68, 0x20, 0x74, 0x65, 0x78, 0x74 }, "English text")] + [InlineData(new byte[] { 0x0C, 0xC4, 0x8C, 0x65, 0x73, 0x6B, 0xC3, 0xBD, 0x20, 0x74, 0x65, 0x78, 0x74 }, "Český text")] + public void ReadString_ReadsUtf8EncodedString(byte[] data, string expectedText) + { + var text = ReadString(data); + + Assert.Equal(expectedText, text); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, false)] + [InlineData(new byte[] { 0x01 }, true)] + public void ReadBoolean_ReadsValuesEncodedAsVarint(byte[] data, bool expectedValue) + { + var value = ReadBoolean(data); + + Assert.Equal(expectedValue, value); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0)] + [InlineData(new byte[] { 0x01 }, -1)] + [InlineData(new byte[] { 0x02 }, 1)] + [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0x0F }, 2147483647)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, -2147483648)] + public void ReadSignedInt_ReadsZiggedVarintValues(byte[] data, int expectedNumber) + { + var number = ReadSignedInt(data); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0)] + [InlineData(new byte[] { 0x01 }, 1)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, -1)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x07 }, 2147483647)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, -2147483648)] + public void ReadInt_ReadsVarintValues(byte[] data, int expectedNumber) + { + var number = ReadInt(data); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0U)] + [InlineData(new byte[] { 0x01 }, 1U)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, 4294967295U)] + public void ReadUint_ReadsVarintValues(byte[] data, uint expectedNumber) + { + var number = ReadUint(data); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0)] + [InlineData(new byte[] { 0x01 }, -1)] + [InlineData(new byte[] { 0x02 }, 1)] + [InlineData(new byte[] { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, 9223372036854775807)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, -9223372036854775808)] + public void ReadSignedLong_ReadsZiggedVarintValues(byte[] data, long expectedNumber) + { + var number = ReadSignedLong(data); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0L)] + [InlineData(new byte[] { 0x01 }, 1L)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, -1L)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, 9223372036854775807)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01 }, -9223372036854775808)] + public void ReadLong_ReadsVarintValues(byte[] data, long expectedNumber) + { + var number = ReadLong(data); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0UL)] + [InlineData(new byte[] { 0x01 }, 1UL)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, 18446744073709551615UL)] + public void ReadULong_ReadsVarintValues(byte[] data, ulong expectedNumber) + { + var number = ReadULong(data); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0f)] + [InlineData(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }, 3.14f)] + [InlineData(new byte[] { 0xF7, 0xCC, 0x12, 0x39 }, 0.00014f)] + [InlineData(new byte[] { 0x66, 0x80, 0x3B, 0x46 }, 12000.1f)] + public void ReadSingle_ReadsValues(byte[] data, float expectedNumber) + { + var number = ReadSingle(data); + + Assert.Equal(expectedNumber, number); + } + + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0d)] + [InlineData(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, 3.14)] + [InlineData(new byte[] { 0xD2, 0xFB, 0xC6, 0xD7, 0x9E, 0x59, 0x22, 0x3F }, 0.00014)] + [InlineData(new byte[] { 0xCD, 0xCC, 0xCC, 0xCC, 0x0C, 0x70, 0xC7, 0x40 }, 12000.1)] + public void ReadDouble_ReadsValues(byte[] data, double expectedNumber) + { + var number = ReadDouble(data); + + Assert.Equal(expectedNumber, number); + } +} diff --git a/src/PbfLite.Tests/PbfReaderTests.cs b/src/PbfLite.Tests/PbfReaderTests.cs new file mode 100644 index 0000000..aeba184 --- /dev/null +++ b/src/PbfLite.Tests/PbfReaderTests.cs @@ -0,0 +1,42 @@ +using Xunit; + +namespace PbfLite.Tests; + +public abstract partial class PbfReaderTests +{ + public abstract (int fieldNumber, WireType wireType) ReadFieldHeader(byte[] data); + + [Theory] + [InlineData(new byte[] { 0x08 }, 1)] + [InlineData(new byte[] { 0x78 }, 15)] + [InlineData(new byte[] { 0x80, 0x01 }, 16)] + public void ReadFieldHeader_ReadsFieldNumbers(byte[] data, int fieldNumber) + { + var header = ReadFieldHeader(data); + + Assert.Equal(fieldNumber, header.fieldNumber); + } + + [Theory] + [InlineData(new byte[] { 0x08 }, WireType.VarInt)] + [InlineData(new byte[] { 0x09 }, WireType.Fixed64)] + [InlineData(new byte[] { 0x0A }, WireType.String)] + [InlineData(new byte[] { 0x0D }, WireType.Fixed32)] + public void ReadFieldHeader_ReadsWireTypes(byte[] data, WireType wireType) + { + var header = ReadFieldHeader(data); + + Assert.Equal(wireType, header.wireType); + } + + [Fact] + public void ReadFieldHeader_ReturnsNoneWhenEndOfBlockIsReached() + { + var reader = PbfBlockReader.Create([]); + + var header = reader.ReadFieldHeader(); + + Assert.Equal(0, header.fieldNumber); + Assert.Equal(WireType.None, header.wireType); + } +} diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs b/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs new file mode 100644 index 0000000..689e462 --- /dev/null +++ b/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace PbfLite.Tests; + +public partial class PbfStreamReaderTests +{ + public class Collections : PbfReaderCollectionsTests + { + private static ReadOnlySpan ReadCollection(byte[] data, WireType wireType, int itemCount, Action> readAction) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + var items = new List(itemCount); + + readAction(reader, wireType, items); + + return CollectionsMarshal.AsSpan(items); + } + + protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadUIntCollection(wireType, items)); + + protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadULongCollection(wireType, items)); + + protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadIntCollection(wireType, items)); + + protected override ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadSignedIntCollection(wireType, items)); + + protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadLongCollection(wireType, items)); + + protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadSignedLongCollection(wireType, items)); + + protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadBooleanCollection(wireType, items)); + + protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadSingleCollection(wireType, items)); + + protected override ReadOnlySpan ReadDoubleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadDoubleCollection(wireType, items)); + } +} \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs b/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs new file mode 100644 index 0000000..0251b5c --- /dev/null +++ b/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using Xunit; + +namespace PbfLite.Tests; + +public partial class PbfStreamReaderTests +{ + public class Primitives : PbfReaderPrimitivesTests + { + public override uint ReadFixed32(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + return reader.ReadFixed32(); + } + + public override ulong ReadFixed64(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + return reader.ReadFixed64(); + } + + public override ReadOnlySpan ReadLengthPrefixedBytes(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + return reader.ReadLengthPrefixedBytes(); + } + + public override uint ReadVarInt32(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + return reader.ReadVarInt32(); + } + + public override ulong ReadVarInt64(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + return reader.ReadVarInt64(); + } + } +} \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.SystemTypes.cs b/src/PbfLite.Tests/PbfStreamReaderTests.SystemTypes.cs new file mode 100644 index 0000000..16c4c85 --- /dev/null +++ b/src/PbfLite.Tests/PbfStreamReaderTests.SystemTypes.cs @@ -0,0 +1,91 @@ +using System; +using System.IO; +using Xunit; + +namespace PbfLite.Tests; + +public partial class PbfStreamReaderTests +{ + public class SystemTypes : PbfReaderSystemTypesTests + { + public override string ReadString(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadString(); + } + + public override bool ReadBoolean(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadBoolean(); + } + + public override int ReadSignedInt(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadSignedInt(); + } + + public override int ReadInt(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadInt(); + } + + public override uint ReadUint(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadUint(); + } + + public override long ReadSignedLong(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadSignedLong(); + } + + public override long ReadLong(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadLong(); + } + + public override ulong ReadULong(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadULong(); + } + + public override float ReadSingle(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadSingle(); + } + + public override double ReadDouble(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadDouble(); + } + } +} \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.cs b/src/PbfLite.Tests/PbfStreamReaderTests.cs new file mode 100644 index 0000000..08a34dd --- /dev/null +++ b/src/PbfLite.Tests/PbfStreamReaderTests.cs @@ -0,0 +1,119 @@ +using System.IO; +using Xunit; + +namespace PbfLite.Tests; + +public partial class PbfStreamReaderTests : PbfReaderTests +{ + public override (int fieldNumber, WireType wireType) ReadFieldHeader(byte[] data) + { + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); + + return reader.ReadFieldHeader(); + } + + [Theory] + [InlineData(new byte[] { 0x00 }, WireType.VarInt, 1)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, WireType.VarInt, 5)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }, WireType.VarInt, 10)] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, WireType.Fixed32, 4)] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, WireType.Fixed64, 8)] + [InlineData(new byte[] { 0x03, 0x41, 0x42, 0x43 }, WireType.String, 4)] + public void SkipFields_SkipsCorrectNumberOfBytes(byte[] data, WireType wireType, int expectedPosition) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + reader.SkipField(wireType); + + Assert.Equal(expectedPosition, stream.Position); + } + } + + [Fact] + public void ReadVarInt32_ReadsMinimalBytes() + { + // Test that ReadVarInt32 reads exactly as many bytes as needed + // 0x05 = varint for value 5 (single byte) + byte[] data = { 0x05, 0xFF }; // 0xFF should not be read + + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var value = reader.ReadVarInt32(); + + Assert.Equal(5U, value); + Assert.Equal(1, stream.Position); // Only one byte should have been read + } + } + + [Fact] + public void ReadVarInt64_ReadsMinimalBytes() + { + // Test that ReadVarInt64 reads exactly as many bytes as needed + // 0x7F = varint for value 127 (single byte) + byte[] data = { 0x7F, 0xFF }; // 0xFF should not be read + + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var value = reader.ReadVarInt64(); + + Assert.Equal(127UL, value); + Assert.Equal(1, stream.Position); // Only one byte should have been read + } + } + + [Fact] + public void ReadFixed32_ReadsExactly4Bytes() + { + byte[] data = { 0x01, 0x02, 0x03, 0x04, 0xFF }; + + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var value = reader.ReadFixed32(); + + Assert.Equal(0x04030201U, value); // Little-endian + Assert.Equal(4, stream.Position); // Exactly 4 bytes read + } + } + + [Fact] + public void ReadFixed64_ReadsExactly8Bytes() + { + byte[] data = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xFF }; + + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var value = reader.ReadFixed64(); + + Assert.Equal(0x0807060504030201UL, value); // Little-endian + Assert.Equal(8, stream.Position); // Exactly 8 bytes read + } + } + + [Fact] + public void ReadLengthPrefixedBytes_ReadsExactLength() + { + // Length = 3, followed by "ABC" + byte[] data = { 0x03, 0x41, 0x42, 0x43, 0xFF }; + + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var bytes = reader.ReadLengthPrefixedBytes(); + + Assert.Equal(new byte[] { 0x41, 0x42, 0x43 }, bytes); + Assert.Equal(4, stream.Position); // 1 (length varint) + 3 (data) bytes read + } + } +} diff --git a/src/PbfLite/PbfBlockReader.Collections.cs b/src/PbfLite/PbfBlockReader.Collections.cs index 291467b..eee3c95 100644 --- a/src/PbfLite/PbfBlockReader.Collections.cs +++ b/src/PbfLite/PbfBlockReader.Collections.cs @@ -11,7 +11,7 @@ private Span ReadScalarCollection(WireType wireType, WireType itemWireType { if (wireType == WireType.String) { - uint byteLength = ReadVarInt32(); + var byteLength = ReadVarInt32(); var endPosition = _position + byteLength; var itemsCount = 0; @@ -36,22 +36,13 @@ private void ReadScalarCollection(WireType wireType, List collection, Wire { if (wireType == WireType.String) { - uint byteLength = ReadVarInt32(); + var byteLength = ReadVarInt32(); var endPosition = _position + byteLength; - // Estimate capacity based on wire type to reduce List reallocations - int estimatedItemSize = itemWireType switch + var estimatedItemsCount = PbfEncoding.EstimateItemsCount(byteLength, itemWireType); + if (collection.Capacity < collection.Count + estimatedItemsCount) { - WireType.VarInt => 2, // Minimum 1 byte per varint, average ~1-5 - WireType.Fixed32 => 4, // Always 4 bytes - WireType.Fixed64 => 8, // Always 8 bytes - _ => 1 - }; - int estimatedCapacity = Math.Max(1, (int)(byteLength / estimatedItemSize)); - - if (collection.Capacity < collection.Count + estimatedCapacity) - { - collection.Capacity = collection.Count + estimatedCapacity; + collection.Capacity = collection.Count + estimatedItemsCount; } while (_position < endPosition) diff --git a/src/PbfLite/PbfBlockReader.SystemTypes.cs b/src/PbfLite/PbfBlockReader.SystemTypes.cs index e003ea4..feec146 100644 --- a/src/PbfLite/PbfBlockReader.SystemTypes.cs +++ b/src/PbfLite/PbfBlockReader.SystemTypes.cs @@ -40,7 +40,7 @@ public bool ReadBoolean() [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadSignedInt() { - return PbfBlockReader.Zag(ReadVarInt32()); + return PbfEncoding.Zag(ReadVarInt32()); } /// @@ -70,7 +70,7 @@ public uint ReadUint() [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadSignedLong() { - return PbfBlockReader.Zag(ReadVarInt64()); + return PbfEncoding.Zag(ReadVarInt64()); } /// diff --git a/src/PbfLite/PbfBlockReader.cs b/src/PbfLite/PbfBlockReader.cs index a2a7486..46f2818 100644 --- a/src/PbfLite/PbfBlockReader.cs +++ b/src/PbfLite/PbfBlockReader.cs @@ -36,20 +36,6 @@ public static PbfBlockReader Create(ReadOnlySpan block) }; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int Zag(uint ziggedValue) - { - int value = (int)ziggedValue; - return (-(value & 0x01)) ^ ((value >> 1) & ~Int32Msb); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static long Zag(ulong ziggedValue) - { - var value = (long)ziggedValue; - return (-(value & 0x01L)) ^ ((value >> 1) & ~Int64Msb); - } - /// /// Reads the next field header from the stream and returns the field /// number and wire type. If the end of the block is reached the diff --git a/src/PbfLite/PbfEncoding.cs b/src/PbfLite/PbfEncoding.cs new file mode 100644 index 0000000..a9a1e2e --- /dev/null +++ b/src/PbfLite/PbfEncoding.cs @@ -0,0 +1,72 @@ +using System; +using System.Runtime.CompilerServices; + +namespace PbfLite; + +/// +/// Internal helper class for shared encoding/decoding logic used by both +/// PbfBlockReader and PbfStreamReader. +/// +internal static class PbfEncoding +{ + private const long Int64Msb = ((long)1) << 63; + private const int Int32Msb = ((int)1) << 31; + + /// + /// Decodes a zigzag-encoded 32-bit unsigned integer to a signed integer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int Zag(uint ziggedValue) + { + int value = (int)ziggedValue; + return (-(value & 0x01)) ^ ((value >> 1) & ~Int32Msb); + } + + /// + /// Decodes a zigzag-encoded 64-bit unsigned integer to a signed integer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static long Zag(ulong ziggedValue) + { + var value = (long)ziggedValue; + return (-(value & 0x01L)) ^ ((value >> 1) & ~Int64Msb); + } + + /// + /// Encodes a field number and wire type into a field header varint. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint EncodeFieldHeader(int fieldNumber, WireType wireType) + { + return ((uint)fieldNumber << 3) | ((uint)wireType & 7); + } + + /// + /// Decodes a field header varint into field number and wire type. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (int fieldNumber, WireType wireType) DecodeFieldHeader(uint header) + { + return ((int)(header >> 3), (WireType)(header & 7)); + } + + /// + /// Estimates the number of items that can be represented by a buffer of the specified byte length and wire type. + /// + /// The total length of the buffer, in bytes, to be analyzed for item estimation. Must be non-negative. + /// The wire type of the items to estimate, which determines the assumed size per item. + /// An estimated count of items that could be contained within the specified byte length for the given wire type. + /// The value is always at least 1. + internal static int EstimateItemsCount(uint byteLength, WireType itemWireType) + { + var estimatedItemSize = itemWireType switch + { + WireType.VarInt => 2, // Minimum 1 byte per varint, average ~1-5 + WireType.Fixed32 => 4, // Always 4 bytes + WireType.Fixed64 => 8, // Always 8 bytes + _ => 1 + }; + + return Math.Max(1, (int)(byteLength / estimatedItemSize)); + } +} diff --git a/src/PbfLite/PbfLite.csproj b/src/PbfLite/PbfLite.csproj index a035c02..b58657d 100644 --- a/src/PbfLite/PbfLite.csproj +++ b/src/PbfLite/PbfLite.csproj @@ -5,7 +5,7 @@ PbfLite - 0.2.1 + 0.3.0 Lukas Kabrt PbfLITE is a low-level .NET Protocol Buffers implementation. It is intended for scenarios where you need fine-grained control over serialization and deserialization without the overhead of reflection, but it requires developers to manually implement the serialization / deserialization logic. MIT diff --git a/src/PbfLite/PbfStreamReader.Collections.cs b/src/PbfLite/PbfStreamReader.Collections.cs new file mode 100644 index 0000000..403a5cc --- /dev/null +++ b/src/PbfLite/PbfStreamReader.Collections.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; + +namespace PbfLite; + +public partial class PbfStreamReader +{ + private delegate T ItemReaderDelegate(PbfStreamReader reader); + + private void ReadScalarCollection(WireType wireType, WireType itemWireType, List collection, ItemReaderDelegate itemReader) + { + if (wireType == WireType.String) + { + var byteLength = ReadVarInt32(); + var endPosition = _stream.Position + byteLength; + + var estimatedItemsCount = PbfEncoding.EstimateItemsCount(byteLength, itemWireType); + if (collection.Capacity < collection.Count + estimatedItemsCount) + { + collection.Capacity = collection.Count + estimatedItemsCount; + } + + while (_stream.Position < endPosition) + { + collection.Add(itemReader(this)); + } + } + else if (wireType == itemWireType) + { + collection.Add(itemReader(this)); + } + else + { + throw new InvalidOperationException($"Cannot read collection with wire type {wireType}"); + } + } + + private static readonly ItemReaderDelegate ReadUintDelegate = static (reader) => reader.ReadUint(); + private static readonly ItemReaderDelegate ReadULongDelegate = static (reader) => reader.ReadULong(); + private static readonly ItemReaderDelegate ReadIntDelegate = static (reader) => reader.ReadInt(); + private static readonly ItemReaderDelegate ReadLongDelegate = static (reader) => reader.ReadLong(); + private static readonly ItemReaderDelegate ReadSignedIntDelegate = static (reader) => reader.ReadSignedInt(); + private static readonly ItemReaderDelegate ReadSignedLongDelegate = static (reader) => reader.ReadSignedLong(); + private static readonly ItemReaderDelegate ReadBooleanDelegate = static (reader) => reader.ReadBoolean(); + private static readonly ItemReaderDelegate ReadSingleDelegate = static (reader) => reader.ReadSingle(); + private static readonly ItemReaderDelegate ReadDoubleDelegate = static (reader) => reader.ReadDouble(); + + /// + /// Reads a collection of unsigned 32-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadUIntCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.VarInt, collection, ReadUintDelegate); + + /// + /// Reads a collection of unsigned 64-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadULongCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.VarInt, collection, ReadULongDelegate); + + /// + /// Reads a collection of signed 32-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadIntCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.VarInt, collection, ReadIntDelegate); + + /// + /// Reads a collection of signed 64-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadLongCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.VarInt, collection, ReadLongDelegate); + + /// + /// Reads a collection of zigzag-encoded signed 32-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadSignedIntCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.VarInt, collection, ReadSignedIntDelegate); + + /// + /// Reads a collection of zigzag-encoded signed 64-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadSignedLongCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.VarInt, collection, ReadSignedLongDelegate); + + /// + /// Reads a collection of boolean values from the stream. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadBooleanCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.VarInt, collection, ReadBooleanDelegate); + + /// + /// Reads a collection of 32-bit floats from the stream. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadSingleCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.Fixed32, collection, ReadSingleDelegate); + + /// + /// Reads a collection of 64-bit floats from the stream. + /// + /// The wire type used to encode the collection. + /// The collection to fill with decoded items. + public void ReadDoubleCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.Fixed64, collection, ReadDoubleDelegate); +} diff --git a/src/PbfLite/PbfStreamReader.SystemTypes.cs b/src/PbfLite/PbfStreamReader.SystemTypes.cs new file mode 100644 index 0000000..0eab2a4 --- /dev/null +++ b/src/PbfLite/PbfStreamReader.SystemTypes.cs @@ -0,0 +1,116 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text; + +namespace PbfLite; + +public partial class PbfStreamReader +{ + /// + /// Reads a UTF-8 encoded length-prefixed string from the stream. + /// + /// The decoded string. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadString() + { + var buffer = ReadLengthPrefixedBytes(); + return Encoding.UTF8.GetString(buffer); + } + + /// + /// Reads a boolean encoded as a varint (0 = false, 1 = true). + /// + /// The boolean value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ReadBoolean() + { + switch (ReadVarInt32()) + { + case 0: return false; + case 1: return true; + default: throw new PbfFormatException($"Data contains invalid value for boolean. Valid values are '0' and '1' encoded as varint."); + } + } + + /// + /// Reads a signed 32-bit integer encoded with zigzag and varint. + /// + /// The decoded signed integer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadSignedInt() + { + return PbfEncoding.Zag(ReadVarInt32()); + } + + /// + /// Reads a 32-bit integer encoded as varint. + /// + /// The integer value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt() + { + return (int)ReadVarInt32(); + } + + /// + /// Reads an unsigned 32-bit integer encoded as varint. + /// + /// The unsigned integer value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUint() + { + return ReadVarInt32(); + } + + /// + /// Reads a signed 64-bit integer encoded with zigzag and varint. + /// + /// The decoded signed long. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadSignedLong() + { + return PbfEncoding.Zag(ReadVarInt64()); + } + + /// + /// Reads a 64-bit integer encoded as varint. + /// + /// The long value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadLong() + { + return (long)ReadVarInt64(); + } + + /// + /// Reads an unsigned 64-bit integer encoded as varint. + /// + /// The unsigned long value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadULong() + { + return ReadVarInt64(); + } + + /// + /// Reads a 32-bit floating point value (IEEE 754 little-endian). + /// + /// The float value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe float ReadSingle() + { + var value = ReadFixed32(); + return *(float*)&value; + } + + /// + /// Reads a 64-bit floating point value (IEEE 754 little-endian). + /// + /// The double value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe double ReadDouble() + { + var value = ReadFixed64(); + return *(double*)&value; + } +} diff --git a/src/PbfLite/PbfStreamReader.cs b/src/PbfLite/PbfStreamReader.cs new file mode 100644 index 0000000..93c2273 --- /dev/null +++ b/src/PbfLite/PbfStreamReader.cs @@ -0,0 +1,333 @@ +using System; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.CompilerServices; + +namespace PbfLite; + +/// +/// Reader for Protocol Buffers binary data from streams. +/// +public partial class PbfStreamReader +{ + private readonly Stream _stream; + + /// + /// Creates a new instance for the provided stream. + /// + /// The stream to read from. + /// Thrown if stream is null. + public PbfStreamReader(Stream stream) + { + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + } + + /// + /// Reads the next field header from the stream and returns the field number + /// and wire type. If the end of the stream is reached, the returned field + /// number will be zero and the wire type will be . + /// + /// Tuple of (fieldNumber, wireType). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public (int fieldNumber, WireType wireType) ReadFieldHeader() + { + if (!TryReadVarInt32(out uint header)) + { + return (0, WireType.None); + } + + if (header != 0) + { + return PbfEncoding.DecodeFieldHeader(header); + } + else + { + return (0, WireType.None); + } + } + + /// + /// Skips a field with the specified wire type. + /// + /// The wire type of the field to skip. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SkipField(WireType wireType) + { + switch (wireType) + { + case WireType.VarInt: + ReadVarInt64(); + break; + case WireType.Fixed32: + ReadFixed32(); + break; + case WireType.Fixed64: + ReadFixed64(); + break; + case WireType.String: + var length = ReadVarInt64(); + SkipBytes((int)length); + break; + default: + throw new ArgumentException($"Unable to skip field. '{wireType}' is unknown WireType"); + } + } + + /// + /// Reads exactly 4 bytes as a little-endian 32-bit unsigned integer. + /// + /// The 32-bit unsigned integer read. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadFixed32() + { + Span buffer = stackalloc byte[4]; + ReadBytes(buffer); + return BinaryPrimitives.ReadUInt32LittleEndian(buffer); + } + + /// + /// Reads exactly 8 bytes as a little-endian 64-bit unsigned integer. + /// + /// The 64-bit unsigned integer read. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadFixed64() + { + Span buffer = stackalloc byte[8]; + ReadBytes(buffer); + return BinaryPrimitives.ReadUInt64LittleEndian(buffer); + } + + /// + /// Reads a 32-bit unsigned integer encoded as a base-128 varint. + /// + /// The decoded 32-bit unsigned integer. + public uint ReadVarInt32() + { + if (!TryReadVarInt32(out uint value)) + { + throw new EndOfStreamException("Unexpected end of stream while reading varint32"); + } + return value; + } + + /// + /// Reads a 64-bit unsigned integer encoded as a base-128 varint. + /// + /// The decoded 64-bit unsigned integer. + public ulong ReadVarInt64() + { + if (!TryReadVarInt64(out ulong value)) + { + throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + } + return value; + } + + /// + /// Reads a length-prefixed sequence of bytes and returns it as a byte array. + /// + /// A byte array containing the length-prefixed data. + public byte[] ReadLengthPrefixedBytes() + { + var length = ReadVarInt32(); + byte[] buffer = new byte[length]; + ReadBytes(buffer); + return buffer; + } + + /// + /// Tries to read a 32-bit unsigned integer encoded as a base-128 varint. + /// + /// The decoded value, or 0 if end of stream reached. + /// True if a complete varint was read; false if end of stream reached before completing the varint. + private bool TryReadVarInt32(out uint value) + { + int byteValue = _stream.ReadByte(); + if (byteValue < 0) + { + value = 0; + return false; + } + + value = (uint)byteValue; + if ((value & 0x80) == 0) + { + return true; + } + + value &= 0x7F; + + uint chunk = (uint)ReadByte(); + value |= (chunk & 0x7F) << 7; + if ((chunk & 0x80) == 0) + { + return true; + } + + chunk = (uint)ReadByte(); + value |= (chunk & 0x7F) << 14; + if ((chunk & 0x80) == 0) + { + return true; + } + + chunk = (uint)ReadByte(); + value |= (chunk & 0x7F) << 21; + if ((chunk & 0x80) == 0) + { + return true; + } + + chunk = (uint)ReadByte(); + value |= chunk << 28; // can only use 4 bits from this chunk + if ((chunk & 0xF0) == 0) + { + return true; + } + + if ((chunk & 0xF0) == 0xF0 && + ReadByte() == 0xFF && + ReadByte() == 0xFF && + ReadByte() == 0xFF && + ReadByte() == 0xFF && + ReadByte() == 0x01) + { + return true; + } + + throw new InvalidOperationException("Malformed varint"); + } + + /// + /// Tries to read a 64-bit unsigned integer encoded as a base-128 varint. + /// + /// The decoded value, or 0 if end of stream reached. + /// True if a complete varint was read; false if end of stream reached before completing the varint. + private bool TryReadVarInt64(out ulong value) + { + int byteValue = _stream.ReadByte(); + if (byteValue < 0) + { + value = 0; + return false; + } + + value = (ulong)byteValue; + if ((value & 0x80) == 0) + { + return true; + } + + value &= 0x7F; + byteValue = ReadByte(); + ulong chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 7; + if ((chunk & 0x80) == 0) + return true; + + byteValue = ReadByte(); + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 14; + if ((chunk & 0x80) == 0) + return true; + + byteValue = ReadByte(); + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 21; + if ((chunk & 0x80) == 0) + return true; + + byteValue = ReadByte(); + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 28; + if ((chunk & 0x80) == 0) + return true; + + byteValue = ReadByte(); + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 35; + if ((chunk & 0x80) == 0) + return true; + + byteValue = ReadByte(); + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 42; + if ((chunk & 0x80) == 0) + return true; + + byteValue = ReadByte(); + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 49; + if ((chunk & 0x80) == 0) + return true; + + byteValue = ReadByte(); + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 56; + if ((chunk & 0x80) == 0) + return true; + + byteValue = ReadByte(); + chunk = (ulong)byteValue; + value |= chunk << 63; + + if ((chunk & ~(ulong)0x01) != 0) + { + throw new InvalidOperationException("Malformed varint"); + } + + return true; + } + + /// + /// Reads a single byte from the stream, throwing if end is reached. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte ReadByte() + { + var byteValue = _stream.ReadByte(); + if (byteValue < 0) + { + throw new EndOfStreamException("Unexpected end of stream"); + } + + return (byte)byteValue; + } + + /// + /// Reads exactly the specified number of bytes into a span. + /// + private void ReadBytes(Span buffer) + { + _stream.ReadExactly(buffer); + } + + /// + /// Skips the specified number of bytes from the stream. + /// + private void SkipBytes(int count) + { + if (_stream.CanSeek) + { + var newPosition = _stream.Position + count; + if (newPosition > _stream.Length) + { + throw new EndOfStreamException($"Unexpected end of stream while skipping {count} bytes"); + } + + _stream.Seek(count, SeekOrigin.Current); + return; + } + + Span buffer = stackalloc byte[Math.Min(4096, count)]; + int remaining = count; + + while (remaining > 0) + { + var toRead = Math.Min(buffer.Length, remaining); + _stream.ReadExactly(buffer.Slice(0, toRead)); + + remaining -= toRead; + } + } +}