From d1ed1f5536f24b4a534b0a7788452e24068eb9f6 Mon Sep 17 00:00:00 2001 From: Lukas Kabrt Date: Thu, 1 Jan 2026 18:17:22 +0100 Subject: [PATCH 1/8] Initial version --- ...bfBlockReaderStreamReaderRoundTripTests.cs | 215 ++++++++++ src/PbfLite.Tests/PbfBlockReaderTests.cs | 4 +- .../PbfStreamReaderTests.Collections.cs | 292 +++++++++++++ .../PbfStreamReaderTests.Primitives.cs | 92 ++++ .../PbfStreamReaderTests.SystemTypes.cs | 176 ++++++++ src/PbfLite.Tests/PbfStreamReaderTests.cs | 209 ++++++++++ src/PbfLite/PbfBlockReader.SystemTypes.cs | 4 +- src/PbfLite/PbfBlockReader.cs | 14 - src/PbfLite/PbfEncodingHelpers.cs | 52 +++ src/PbfLite/PbfStreamReader.Collections.cs | 128 ++++++ src/PbfLite/PbfStreamReader.SystemTypes.cs | 138 ++++++ src/PbfLite/PbfStreamReader.cs | 394 ++++++++++++++++++ 12 files changed, 1700 insertions(+), 18 deletions(-) create mode 100644 src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs create mode 100644 src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs create mode 100644 src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs create mode 100644 src/PbfLite.Tests/PbfStreamReaderTests.SystemTypes.cs create mode 100644 src/PbfLite.Tests/PbfStreamReaderTests.cs create mode 100644 src/PbfLite/PbfEncodingHelpers.cs create mode 100644 src/PbfLite/PbfStreamReader.Collections.cs create mode 100644 src/PbfLite/PbfStreamReader.SystemTypes.cs create mode 100644 src/PbfLite/PbfStreamReader.cs diff --git a/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs b/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs new file mode 100644 index 0000000..a30b772 --- /dev/null +++ b/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs @@ -0,0 +1,215 @@ +using System; +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 = streamReader.ReadUIntCollection(WireType.String); + + // 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.cs b/src/PbfLite.Tests/PbfBlockReaderTests.cs index 71f76bd..ca86e7c 100644 --- a/src/PbfLite.Tests/PbfBlockReaderTests.cs +++ b/src/PbfLite.Tests/PbfBlockReaderTests.cs @@ -66,7 +66,7 @@ public void SkipFields_SkipsCorrectNumberOfBytes(byte[] data, WireType wireType, [InlineData(4294967295, -2147483648)] public void Zag_Decodes32BitZiggedValues(uint encodedNumber, int expectedNumber) { - var number = PbfBlockReader.Zag(encodedNumber); + var number = PbfEncodingHelpers.Zag(encodedNumber); Assert.Equal(expectedNumber, number); } @@ -79,7 +79,7 @@ public void Zag_Decodes32BitZiggedValues(uint encodedNumber, int expectedNumber) [InlineData(18446744073709551615UL, -9223372036854775808L)] public void Zag_Decodes64BitZiggedValues(ulong encodedNumber, long expectedNumber) { - var number = PbfBlockReader.Zag(encodedNumber); + var number = PbfEncodingHelpers.Zag(encodedNumber); Assert.Equal(expectedNumber, number); } diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs b/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs new file mode 100644 index 0000000..ee58268 --- /dev/null +++ b/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace PbfLite.Tests; + +public class PbfStreamReaderTests_Collections +{ + [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_ReadsDataSerializedAsSingleElement(byte[] data, uint[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadUIntCollection(WireType.VarInt); + + Assert.Equal(expectedItems, 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_ReadsDataSerializedAsLengthPrefixed(byte[] data, uint[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadUIntCollection(WireType.String); + + Assert.Equal(expectedItems, 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_ReadsDataSerializedAsSingleElement(byte[] data, ulong[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadULongCollection(WireType.VarInt); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new ulong[] { 0 })] + [InlineData(new byte[] { 0x01, 0x01 }, new ulong[] { 1 })] + public void ReadULongCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, ulong[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadULongCollection(WireType.String); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new int[] { 0 })] + [InlineData(new byte[] { 0x01 }, new int[] { 1 })] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -1 })] + public void ReadIntCollection_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadIntCollection(WireType.VarInt); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new int[] { 0 })] + [InlineData(new byte[] { 0x01, 0x01 }, new int[] { 1 })] + [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -1 })] + public void ReadIntCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadIntCollection(WireType.String); + + Assert.Equal(expectedItems, 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 })] + public void ReadLongCollection_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadLongCollection(WireType.VarInt); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x00 }, new long[] { 0 })] + [InlineData(new byte[] { 0x01, 0x01 }, new long[] { 1 })] + public void ReadLongCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadLongCollection(WireType.String); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new int[] { 0 })] + [InlineData(new byte[] { 0x01 }, new int[] { -1 })] + [InlineData(new byte[] { 0x02 }, new int[] { 1 })] + public void ReadSignedIntCollection_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadSignedIntCollection(WireType.VarInt); + + Assert.Equal(expectedItems, 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 })] + public void ReadSignedIntCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadSignedIntCollection(WireType.String); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new long[] { 0 })] + [InlineData(new byte[] { 0x01 }, new long[] { -1 })] + [InlineData(new byte[] { 0x02 }, new long[] { 1 })] + public void ReadSignedLongCollection_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadSignedLongCollection(WireType.VarInt); + + Assert.Equal(expectedItems, 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 })] + public void ReadSignedLongCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadSignedLongCollection(WireType.String); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x00 }, new bool[] { false })] + [InlineData(new byte[] { 0x01 }, new bool[] { true })] + public void ReadBooleanCollection_ReadsDataSerializedAsSingleElement(byte[] data, bool[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadBooleanCollection(WireType.VarInt); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x02, 0x00, 0x01 }, new bool[] { false, true })] + [InlineData(new byte[] { 0x04, 0x01, 0x00, 0x01, 0x00 }, new bool[] { true, false, true, false })] + public void ReadBooleanCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, bool[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadBooleanCollection(WireType.String); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0f })] + [InlineData(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 3.14f })] + public void ReadSingleCollection_ReadsDataSerializedAsSingleElement(byte[] data, float[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadSingleCollection(WireType.Fixed32); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x04, 0x00, 0x00, 0x00, 0x00 }, new float[] { 0f })] + [InlineData(new byte[] { 0x08, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 0f, 3.14f })] + public void ReadSingleCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, float[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadSingleCollection(WireType.String); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0d })] + [InlineData(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 3.14 })] + public void ReadDoubleCollection_ReadsDataSerializedAsSingleElement(byte[] data, double[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadDoubleCollection(WireType.Fixed64); + + Assert.Equal(expectedItems, items); + } + } + + [Theory] + [InlineData(new byte[] { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0d })] + [InlineData(new byte[] { 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 0d, 3.14 })] + public void ReadDoubleCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, double[] expectedItems) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var items = reader.ReadDoubleCollection(WireType.String); + + Assert.Equal(expectedItems, items); + } + } +} diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs b/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs new file mode 100644 index 0000000..ad5459a --- /dev/null +++ b/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs @@ -0,0 +1,92 @@ +using System; +using System.IO; +using Xunit; + +namespace PbfLite.Tests; + +public class PbfStreamReaderTests_Primitives +{ + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0U)] + [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 1U)] + [InlineData(new byte[] { 0xFF, 0x00, 0x00, 0x00 }, 255U)] + [InlineData(new byte[] { 0x00, 0x01, 0x00, 0x00 }, 256U)] + [InlineData(new byte[] { 0x00, 0x00, 0x01, 0x00 }, 65536U)] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x01 }, 16777216U)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, 4294967295U)] + public void ReadFixed32_ReadsNumbers(byte[] data, uint expectedNumber) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadFixed32(); + + Assert.Equal(expectedNumber, number); + } + } + + [Theory] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0UL)] + [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 1UL)] + [InlineData(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 255UL)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }, 4294967295UL)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, 18446744073709551615UL)] + public void ReadFixed64_ReadsNumbers(byte[] data, ulong expectedNumber) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadFixed64(); + + Assert.Equal(expectedNumber, number); + } + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0U)] + [InlineData(new byte[] { 0x01 }, 1U)] + [InlineData(new byte[] { 0x7F }, 127U)] + [InlineData(new byte[] { 0x80, 0x01 }, 128U)] + [InlineData(new byte[] { 0x80, 0x80, 0x01 }, 16384U)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x01 }, 2097152U)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, 268435456U)] + [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, 4294967295U)] + public void ReadVarint32_ReadsNumbers(byte[] data, uint expectedNumber) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadVarInt32(); + + Assert.Equal(expectedNumber, number); + } + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0UL)] + [InlineData(new byte[] { 0x01 }, 1UL)] + [InlineData(new byte[] { 0x7F }, 127UL)] + [InlineData(new byte[] { 0x80, 0x01 }, 128UL)] + [InlineData(new byte[] { 0x80, 0x80, 0x01 }, 16384UL)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x01 }, 2097152UL)] + [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, 268435456UL)] + [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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadVarInt64(); + + Assert.Equal(expectedNumber, number); + } + } +} diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.SystemTypes.cs b/src/PbfLite.Tests/PbfStreamReaderTests.SystemTypes.cs new file mode 100644 index 0000000..27e5a9e --- /dev/null +++ b/src/PbfLite.Tests/PbfStreamReaderTests.SystemTypes.cs @@ -0,0 +1,176 @@ +using System; +using System.IO; +using Xunit; + +namespace PbfLite.Tests; + +public class PbfStreamReaderTests_SystemTypes +{ + [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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var text = reader.ReadString(); + + Assert.Equal(expectedText, text); + } + } + + [Theory] + [InlineData(new byte[] { 0x00 }, false)] + [InlineData(new byte[] { 0x01 }, true)] + public void ReadBoolean_ReadsValuesEncodedAsVarint(byte[] data, bool expectedValue) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var value = reader.ReadBoolean(); + + 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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadSignedInt(); + + 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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadInt(); + + 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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadUint(); + + Assert.Equal(expectedNumber, number); + } + } + + [Theory] + [InlineData(new byte[] { 0x00 }, 0L)] + [InlineData(new byte[] { 0x01 }, -1L)] + [InlineData(new byte[] { 0x02 }, 1L)] + [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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadSignedLong(); + + 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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadLong(); + + 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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadULong(); + + 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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadSingle(); + + 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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var number = reader.ReadDouble(); + + Assert.Equal(expectedNumber, number); + } + } +} diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.cs b/src/PbfLite.Tests/PbfStreamReaderTests.cs new file mode 100644 index 0000000..413e687 --- /dev/null +++ b/src/PbfLite.Tests/PbfStreamReaderTests.cs @@ -0,0 +1,209 @@ +using System; +using System.IO; +using Xunit; + +namespace PbfLite.Tests; + +public class PbfStreamReaderTests +{ + [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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + 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) + { + using (var stream = new MemoryStream(data)) + { + var reader = new PbfStreamReader(stream); + + var header = reader.ReadFieldHeader(); + + Assert.Equal(wireType, header.wireType); + } + } + + [Fact] + public void ReadFieldHeader_ReturnsNoneWhenEndOfStreamIsReached() + { + using (var stream = new MemoryStream(Array.Empty())) + { + var reader = new PbfStreamReader(stream); + + var header = reader.ReadFieldHeader(); + + Assert.Equal(0, header.fieldNumber); + Assert.Equal(WireType.None, header.wireType); + } + } + + [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); + } + } + + [Theory] + [InlineData(0, 0)] + [InlineData(1, -1)] + [InlineData(2, 1)] + [InlineData(4294967294U, 2147483647)] + [InlineData(4294967295U, -2147483648)] + public void Zag_Decodes32BitZiggedValues(uint encodedNumber, int expectedNumber) + { + var number = PbfEncodingHelpers.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 = PbfEncodingHelpers.Zag(encodedNumber); + + Assert.Equal(expectedNumber, number); + } + + [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 + } + } + + [Fact] + public void Dispose_ClosesStream() + { + var stream = new MemoryStream(new byte[] { 0x00 }); + var reader = new PbfStreamReader(stream); + + reader.Dispose(); + + // Stream should be closed + Assert.Throws(() => stream.WriteByte(0)); + } + + [Fact] + public void ThrowsAfterDispose() + { + var stream = new MemoryStream(new byte[] { 0x00 }); + var reader = new PbfStreamReader(stream); + + reader.Dispose(); + + // Operations should throw ObjectDisposedException + Assert.Throws(() => reader.ReadFieldHeader()); + } +} diff --git a/src/PbfLite/PbfBlockReader.SystemTypes.cs b/src/PbfLite/PbfBlockReader.SystemTypes.cs index e003ea4..31a6040 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 PbfEncodingHelpers.Zag(ReadVarInt32()); } /// @@ -70,7 +70,7 @@ public uint ReadUint() [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadSignedLong() { - return PbfBlockReader.Zag(ReadVarInt64()); + return PbfEncodingHelpers.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/PbfEncodingHelpers.cs b/src/PbfLite/PbfEncodingHelpers.cs new file mode 100644 index 0000000..3ddaa0a --- /dev/null +++ b/src/PbfLite/PbfEncodingHelpers.cs @@ -0,0 +1,52 @@ +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 PbfEncodingHelpers +{ + 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)); + } +} diff --git a/src/PbfLite/PbfStreamReader.Collections.cs b/src/PbfLite/PbfStreamReader.Collections.cs new file mode 100644 index 0000000..3d0c393 --- /dev/null +++ b/src/PbfLite/PbfStreamReader.Collections.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; + +namespace PbfLite; + +public partial class PbfStreamReader +{ + private delegate T ItemReaderDelegate(PbfStreamReader reader); + + private List ReadScalarCollection(WireType wireType, WireType itemWireType, ItemReaderDelegate itemReader) + { + var collection = new List(); + + if (wireType == WireType.String) + { + uint byteLength = ReadVarInt32(); + long endPosition = _stream.Position + byteLength; + + // Estimate capacity based on wire type to reduce List reallocations + int 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 + }; + int estimatedCapacity = Math.Max(1, (int)(byteLength / estimatedItemSize)); + collection.Capacity = estimatedCapacity; + + 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}"); + } + + return collection; + } + + 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. + /// A list of decoded unsigned 32-bit integers. + public List ReadUIntCollection(WireType wireType) => + ReadScalarCollection(wireType, WireType.VarInt, ReadUintDelegate); + + /// + /// Reads a collection of unsigned 64-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// A list of decoded unsigned 64-bit integers. + public List ReadULongCollection(WireType wireType) => + ReadScalarCollection(wireType, WireType.VarInt, ReadULongDelegate); + + /// + /// Reads a collection of signed 32-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// A list of decoded signed 32-bit integers. + public List ReadIntCollection(WireType wireType) => + ReadScalarCollection(wireType, WireType.VarInt, ReadIntDelegate); + + /// + /// Reads a collection of signed 64-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// A list of decoded signed 64-bit integers. + public List ReadLongCollection(WireType wireType) => + ReadScalarCollection(wireType, WireType.VarInt, ReadLongDelegate); + + /// + /// Reads a collection of zigzag-encoded signed 32-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// A list of decoded zigzag-encoded signed 32-bit integers. + public List ReadSignedIntCollection(WireType wireType) => + ReadScalarCollection(wireType, WireType.VarInt, ReadSignedIntDelegate); + + /// + /// Reads a collection of zigzag-encoded signed 64-bit integers from the stream. + /// + /// The wire type used to encode the collection. + /// A list of decoded zigzag-encoded signed 64-bit integers. + public List ReadSignedLongCollection(WireType wireType) => + ReadScalarCollection(wireType, WireType.VarInt, ReadSignedLongDelegate); + + /// + /// Reads a collection of boolean values from the stream. + /// + /// The wire type used to encode the collection. + /// A list of decoded boolean values. + public List ReadBooleanCollection(WireType wireType) => + ReadScalarCollection(wireType, WireType.VarInt, ReadBooleanDelegate); + + /// + /// Reads a collection of 32-bit floats from the stream. + /// + /// The wire type used to encode the collection. + /// A list of decoded 32-bit floats. + public List ReadSingleCollection(WireType wireType) => + ReadScalarCollection(wireType, WireType.Fixed32, ReadSingleDelegate); + + /// + /// Reads a collection of 64-bit floats from the stream. + /// + /// The wire type used to encode the collection. + /// A list of decoded 64-bit floats. + public List ReadDoubleCollection(WireType wireType) => + ReadScalarCollection(wireType, WireType.Fixed64, ReadDoubleDelegate); +} diff --git a/src/PbfLite/PbfStreamReader.SystemTypes.cs b/src/PbfLite/PbfStreamReader.SystemTypes.cs new file mode 100644 index 0000000..c37d270 --- /dev/null +++ b/src/PbfLite/PbfStreamReader.SystemTypes.cs @@ -0,0 +1,138 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text; + +namespace PbfLite; + +public partial class PbfStreamReader +{ + private static readonly Encoding encoding = Encoding.UTF8; + + /// + /// Reads a UTF-8 encoded length-prefixed string from the stream. + /// + /// The decoded string. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadString() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + var buffer = ReadLengthPrefixedBytes(); + return encoding.GetString(buffer); + } + + /// + /// Reads a boolean encoded as a varint (0 = false, 1 = true). + /// + /// The boolean value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ReadBoolean() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + 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() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + return PbfEncodingHelpers.Zag(ReadVarInt32()); + } + + /// + /// Reads a 32-bit integer encoded as varint. + /// + /// The integer value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + return (int)ReadVarInt32(); + } + + /// + /// Reads an unsigned 32-bit integer encoded as varint. + /// + /// The unsigned integer value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUint() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + return ReadVarInt32(); + } + + /// + /// Reads a signed 64-bit integer encoded with zigzag and varint. + /// + /// The decoded signed long. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadSignedLong() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + return PbfEncodingHelpers.Zag(ReadVarInt64()); + } + + /// + /// Reads a 64-bit integer encoded as varint. + /// + /// The long value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadLong() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + return (long)ReadVarInt64(); + } + + /// + /// Reads an unsigned 64-bit integer encoded as varint. + /// + /// The unsigned long value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadULong() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + return ReadVarInt64(); + } + + /// + /// Reads a 32-bit floating point value (IEEE 754 little-endian). + /// + /// The float value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe float ReadSingle() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + 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() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + var value = ReadFixed64(); + return *(double*)&value; + } +} diff --git a/src/PbfLite/PbfStreamReader.cs b/src/PbfLite/PbfStreamReader.cs new file mode 100644 index 0000000..e975e0f --- /dev/null +++ b/src/PbfLite/PbfStreamReader.cs @@ -0,0 +1,394 @@ +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; + private bool _disposed; + + /// + /// 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)); + _disposed = false; + } + + /// + /// 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() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + if (!TryReadVarInt32(out uint header)) + { + return (0, WireType.None); + } + + if (header != 0) + { + return ((int)(header >> 3), (WireType)(header & 7)); + } + 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) + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + 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() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + Span buffer = stackalloc byte[4]; + ReadExactBytes(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() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + Span buffer = stackalloc byte[8]; + ReadExactBytes(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() + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + var length = ReadVarInt32(); + byte[] buffer = new byte[length]; + ReadExactBytes(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) + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + 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)ReadByteOrThrow(); + value |= (chunk & 0x7F) << 7; + if ((chunk & 0x80) == 0) + { + return true; + } + + chunk = (uint)ReadByteOrThrow(); + value |= (chunk & 0x7F) << 14; + if ((chunk & 0x80) == 0) + { + return true; + } + + chunk = (uint)ReadByteOrThrow(); + value |= (chunk & 0x7F) << 21; + if ((chunk & 0x80) == 0) + { + return true; + } + + chunk = (uint)ReadByteOrThrow(); + value |= chunk << 28; // can only use 4 bits from this chunk + if ((chunk & 0xF0) == 0) + { + return true; + } + + if ((chunk & 0xF0) == 0xF0 && + ReadByteOrThrow() == 0xFF && + ReadByteOrThrow() == 0xFF && + ReadByteOrThrow() == 0xFF && + ReadByteOrThrow() == 0xFF && + ReadByteOrThrow() == 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) + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + int byteValue = _stream.ReadByte(); + if (byteValue < 0) + { + value = 0; + return false; + } + + value = (ulong)byteValue; + if ((value & 0x80) == 0) + { + return true; + } + + value &= 0x7F; + + byteValue = _stream.ReadByte(); + if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + + ulong chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 7; + if ((chunk & 0x80) == 0) + return true; + + byteValue = _stream.ReadByte(); + if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 14; + if ((chunk & 0x80) == 0) + return true; + + byteValue = _stream.ReadByte(); + if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 21; + if ((chunk & 0x80) == 0) + return true; + + byteValue = _stream.ReadByte(); + if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 28; + if ((chunk & 0x80) == 0) + return true; + + byteValue = _stream.ReadByte(); + if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 35; + if ((chunk & 0x80) == 0) + return true; + + byteValue = _stream.ReadByte(); + if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 42; + if ((chunk & 0x80) == 0) + return true; + + byteValue = _stream.ReadByte(); + if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 49; + if ((chunk & 0x80) == 0) + return true; + + byteValue = _stream.ReadByte(); + if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + + chunk = (ulong)byteValue; + value |= (chunk & 0x7F) << 56; + if ((chunk & 0x80) == 0) + return true; + + byteValue = _stream.ReadByte(); + if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); + + 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 ReadByteOrThrow() + { + int 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 ReadExactBytes(Span buffer) + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + int totalRead = 0; + while (totalRead < buffer.Length) + { + int read = _stream.Read(buffer.Slice(totalRead)); + if (read == 0) + throw new EndOfStreamException($"Unexpected end of stream. Expected {buffer.Length} bytes, but got {totalRead}"); + totalRead += read; + } + } + + /// + /// Skips the specified number of bytes from the stream. + /// + private void SkipBytes(int count) + { + ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); + + if (_stream.CanSeek) + { + long 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) + { + int toRead = Math.Min(buffer.Length, remaining); + int read = _stream.Read(buffer.Slice(0, toRead)); + if (read == 0) + { + throw new EndOfStreamException($"Unexpected end of stream while skipping {count} bytes"); + } + remaining -= read; + } + } + + /// + /// Closes the stream and releases resources. + /// + public void Dispose() + { + if (!_disposed) + { + _stream?.Dispose(); + _disposed = true; + } + } +} From 0e213dcacea35aa61effb3dd4d88157dbb8a1cdd Mon Sep 17 00:00:00 2001 From: Lukas Kabrt Date: Sat, 3 Jan 2026 13:53:35 +0100 Subject: [PATCH 2/8] Cleanup --- src/PbfLite.Tests/PbfBlockReaderTests.cs | 4 +- src/PbfLite.Tests/PbfStreamReaderTests.cs | 4 +- src/PbfLite/PbfBlockReader.Collections.cs | 19 +-- src/PbfLite/PbfBlockReader.SystemTypes.cs | 4 +- .../{PbfEncodingHelpers.cs => PbfEncoding.cs} | 22 +++- src/PbfLite/PbfStreamReader.Collections.cs | 17 +-- src/PbfLite/PbfStreamReader.SystemTypes.cs | 24 +--- src/PbfLite/PbfStreamReader.cs | 121 +++++------------- 8 files changed, 74 insertions(+), 141 deletions(-) rename src/PbfLite/{PbfEncodingHelpers.cs => PbfEncoding.cs} (59%) diff --git a/src/PbfLite.Tests/PbfBlockReaderTests.cs b/src/PbfLite.Tests/PbfBlockReaderTests.cs index ca86e7c..7cb9ee2 100644 --- a/src/PbfLite.Tests/PbfBlockReaderTests.cs +++ b/src/PbfLite.Tests/PbfBlockReaderTests.cs @@ -66,7 +66,7 @@ public void SkipFields_SkipsCorrectNumberOfBytes(byte[] data, WireType wireType, [InlineData(4294967295, -2147483648)] public void Zag_Decodes32BitZiggedValues(uint encodedNumber, int expectedNumber) { - var number = PbfEncodingHelpers.Zag(encodedNumber); + var number = PbfEncoding.Zag(encodedNumber); Assert.Equal(expectedNumber, number); } @@ -79,7 +79,7 @@ public void Zag_Decodes32BitZiggedValues(uint encodedNumber, int expectedNumber) [InlineData(18446744073709551615UL, -9223372036854775808L)] public void Zag_Decodes64BitZiggedValues(ulong encodedNumber, long expectedNumber) { - var number = PbfEncodingHelpers.Zag(encodedNumber); + var number = PbfEncoding.Zag(encodedNumber); Assert.Equal(expectedNumber, number); } diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.cs b/src/PbfLite.Tests/PbfStreamReaderTests.cs index 413e687..8c52f69 100644 --- a/src/PbfLite.Tests/PbfStreamReaderTests.cs +++ b/src/PbfLite.Tests/PbfStreamReaderTests.cs @@ -80,7 +80,7 @@ public void SkipFields_SkipsCorrectNumberOfBytes(byte[] data, WireType wireType, [InlineData(4294967295U, -2147483648)] public void Zag_Decodes32BitZiggedValues(uint encodedNumber, int expectedNumber) { - var number = PbfEncodingHelpers.Zag(encodedNumber); + var number = PbfEncoding.Zag(encodedNumber); Assert.Equal(expectedNumber, number); } @@ -93,7 +93,7 @@ public void Zag_Decodes32BitZiggedValues(uint encodedNumber, int expectedNumber) [InlineData(18446744073709551615UL, -9223372036854775808L)] public void Zag_Decodes64BitZiggedValues(ulong encodedNumber, long expectedNumber) { - var number = PbfEncodingHelpers.Zag(encodedNumber); + var number = PbfEncoding.Zag(encodedNumber); Assert.Equal(expectedNumber, number); } 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 31a6040..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 PbfEncodingHelpers.Zag(ReadVarInt32()); + return PbfEncoding.Zag(ReadVarInt32()); } /// @@ -70,7 +70,7 @@ public uint ReadUint() [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadSignedLong() { - return PbfEncodingHelpers.Zag(ReadVarInt64()); + return PbfEncoding.Zag(ReadVarInt64()); } /// diff --git a/src/PbfLite/PbfEncodingHelpers.cs b/src/PbfLite/PbfEncoding.cs similarity index 59% rename from src/PbfLite/PbfEncodingHelpers.cs rename to src/PbfLite/PbfEncoding.cs index 3ddaa0a..a9a1e2e 100644 --- a/src/PbfLite/PbfEncodingHelpers.cs +++ b/src/PbfLite/PbfEncoding.cs @@ -7,7 +7,7 @@ namespace PbfLite; /// Internal helper class for shared encoding/decoding logic used by both /// PbfBlockReader and PbfStreamReader. /// -internal static class PbfEncodingHelpers +internal static class PbfEncoding { private const long Int64Msb = ((long)1) << 63; private const int Int32Msb = ((int)1) << 31; @@ -49,4 +49,24 @@ internal static (int fieldNumber, WireType wireType) DecodeFieldHeader(uint head { 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/PbfStreamReader.Collections.cs b/src/PbfLite/PbfStreamReader.Collections.cs index 3d0c393..8d66319 100644 --- a/src/PbfLite/PbfStreamReader.Collections.cs +++ b/src/PbfLite/PbfStreamReader.Collections.cs @@ -13,19 +13,14 @@ private List ReadScalarCollection(WireType wireType, WireType itemWireType if (wireType == WireType.String) { - uint byteLength = ReadVarInt32(); - long endPosition = _stream.Position + byteLength; + var byteLength = ReadVarInt32(); + var endPosition = _stream.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)); - collection.Capacity = estimatedCapacity; + collection.Capacity = collection.Count + estimatedItemsCount; + } while (_stream.Position < endPosition) { diff --git a/src/PbfLite/PbfStreamReader.SystemTypes.cs b/src/PbfLite/PbfStreamReader.SystemTypes.cs index c37d270..96800b5 100644 --- a/src/PbfLite/PbfStreamReader.SystemTypes.cs +++ b/src/PbfLite/PbfStreamReader.SystemTypes.cs @@ -15,8 +15,6 @@ public partial class PbfStreamReader [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadString() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - var buffer = ReadLengthPrefixedBytes(); return encoding.GetString(buffer); } @@ -28,8 +26,6 @@ public string ReadString() [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ReadBoolean() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - switch (ReadVarInt32()) { case 0: return false; @@ -45,9 +41,7 @@ public bool ReadBoolean() [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadSignedInt() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - - return PbfEncodingHelpers.Zag(ReadVarInt32()); + return PbfEncoding.Zag(ReadVarInt32()); } /// @@ -57,8 +51,6 @@ public int ReadSignedInt() [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadInt() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - return (int)ReadVarInt32(); } @@ -69,8 +61,6 @@ public int ReadInt() [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint ReadUint() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - return ReadVarInt32(); } @@ -81,9 +71,7 @@ public uint ReadUint() [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadSignedLong() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - - return PbfEncodingHelpers.Zag(ReadVarInt64()); + return PbfEncoding.Zag(ReadVarInt64()); } /// @@ -93,8 +81,6 @@ public long ReadSignedLong() [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadLong() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - return (long)ReadVarInt64(); } @@ -105,8 +91,6 @@ public long ReadLong() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong ReadULong() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - return ReadVarInt64(); } @@ -117,8 +101,6 @@ public ulong ReadULong() [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe float ReadSingle() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - var value = ReadFixed32(); return *(float*)&value; } @@ -130,8 +112,6 @@ public unsafe float ReadSingle() [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe double ReadDouble() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - var value = ReadFixed64(); return *(double*)&value; } diff --git a/src/PbfLite/PbfStreamReader.cs b/src/PbfLite/PbfStreamReader.cs index e975e0f..a64b239 100644 --- a/src/PbfLite/PbfStreamReader.cs +++ b/src/PbfLite/PbfStreamReader.cs @@ -11,7 +11,6 @@ namespace PbfLite; public partial class PbfStreamReader { private readonly Stream _stream; - private bool _disposed; /// /// Creates a new instance for the provided stream. @@ -21,7 +20,6 @@ public partial class PbfStreamReader public PbfStreamReader(Stream stream) { _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _disposed = false; } /// @@ -33,8 +31,6 @@ public PbfStreamReader(Stream stream) [MethodImpl(MethodImplOptions.AggressiveInlining)] public (int fieldNumber, WireType wireType) ReadFieldHeader() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - if (!TryReadVarInt32(out uint header)) { return (0, WireType.None); @@ -42,7 +38,7 @@ public PbfStreamReader(Stream stream) if (header != 0) { - return ((int)(header >> 3), (WireType)(header & 7)); + return PbfEncoding.DecodeFieldHeader(header); } else { @@ -57,8 +53,6 @@ public PbfStreamReader(Stream stream) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SkipField(WireType wireType) { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - switch (wireType) { case WireType.VarInt: @@ -86,10 +80,8 @@ public void SkipField(WireType wireType) [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint ReadFixed32() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - Span buffer = stackalloc byte[4]; - ReadExactBytes(buffer); + ReadBytes(buffer); return BinaryPrimitives.ReadUInt32LittleEndian(buffer); } @@ -100,10 +92,8 @@ public uint ReadFixed32() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong ReadFixed64() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - Span buffer = stackalloc byte[8]; - ReadExactBytes(buffer); + ReadBytes(buffer); return BinaryPrimitives.ReadUInt64LittleEndian(buffer); } @@ -139,11 +129,9 @@ public ulong ReadVarInt64() /// A byte array containing the length-prefixed data. public byte[] ReadLengthPrefixedBytes() { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - var length = ReadVarInt32(); byte[] buffer = new byte[length]; - ReadExactBytes(buffer); + ReadBytes(buffer); return buffer; } @@ -154,8 +142,6 @@ public byte[] ReadLengthPrefixedBytes() /// True if a complete varint was read; false if end of stream reached before completing the varint. private bool TryReadVarInt32(out uint value) { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - int byteValue = _stream.ReadByte(); if (byteValue < 0) { @@ -170,29 +156,29 @@ private bool TryReadVarInt32(out uint value) } value &= 0x7F; - - uint chunk = (uint)ReadByteOrThrow(); + + uint chunk = (uint)ReadByte(); value |= (chunk & 0x7F) << 7; if ((chunk & 0x80) == 0) { return true; } - - chunk = (uint)ReadByteOrThrow(); + + chunk = (uint)ReadByte(); value |= (chunk & 0x7F) << 14; if ((chunk & 0x80) == 0) { return true; } - chunk = (uint)ReadByteOrThrow(); + chunk = (uint)ReadByte(); value |= (chunk & 0x7F) << 21; if ((chunk & 0x80) == 0) { return true; } - chunk = (uint)ReadByteOrThrow(); + chunk = (uint)ReadByte(); value |= chunk << 28; // can only use 4 bits from this chunk if ((chunk & 0xF0) == 0) { @@ -200,11 +186,11 @@ private bool TryReadVarInt32(out uint value) } if ((chunk & 0xF0) == 0xF0 && - ReadByteOrThrow() == 0xFF && - ReadByteOrThrow() == 0xFF && - ReadByteOrThrow() == 0xFF && - ReadByteOrThrow() == 0xFF && - ReadByteOrThrow() == 0x01) + ReadByte() == 0xFF && + ReadByte() == 0xFF && + ReadByte() == 0xFF && + ReadByte() == 0xFF && + ReadByte() == 0x01) { return true; } @@ -219,8 +205,6 @@ private bool TryReadVarInt32(out uint value) /// True if a complete varint was read; false if end of stream reached before completing the varint. private bool TryReadVarInt64(out ulong value) { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - int byteValue = _stream.ReadByte(); if (byteValue < 0) { @@ -235,74 +219,55 @@ private bool TryReadVarInt64(out ulong value) } value &= 0x7F; - - byteValue = _stream.ReadByte(); - if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); - + byteValue = ReadByte(); ulong chunk = (ulong)byteValue; value |= (chunk & 0x7F) << 7; if ((chunk & 0x80) == 0) return true; - byteValue = _stream.ReadByte(); - if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); - + byteValue = ReadByte(); chunk = (ulong)byteValue; value |= (chunk & 0x7F) << 14; if ((chunk & 0x80) == 0) return true; - byteValue = _stream.ReadByte(); - if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); - + byteValue = ReadByte(); chunk = (ulong)byteValue; value |= (chunk & 0x7F) << 21; if ((chunk & 0x80) == 0) return true; - byteValue = _stream.ReadByte(); - if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); - + byteValue = ReadByte(); chunk = (ulong)byteValue; value |= (chunk & 0x7F) << 28; if ((chunk & 0x80) == 0) return true; - byteValue = _stream.ReadByte(); - if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); - + byteValue = ReadByte(); chunk = (ulong)byteValue; value |= (chunk & 0x7F) << 35; if ((chunk & 0x80) == 0) return true; - byteValue = _stream.ReadByte(); - if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); - + byteValue = ReadByte(); chunk = (ulong)byteValue; value |= (chunk & 0x7F) << 42; if ((chunk & 0x80) == 0) return true; - byteValue = _stream.ReadByte(); - if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); - + byteValue = ReadByte(); chunk = (ulong)byteValue; value |= (chunk & 0x7F) << 49; if ((chunk & 0x80) == 0) return true; - byteValue = _stream.ReadByte(); - if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); - + byteValue = ReadByte(); chunk = (ulong)byteValue; value |= (chunk & 0x7F) << 56; if ((chunk & 0x80) == 0) return true; - byteValue = _stream.ReadByte(); - if (byteValue < 0) throw new EndOfStreamException("Unexpected end of stream while reading varint64"); - + byteValue = ReadByte(); chunk = (ulong)byteValue; value |= chunk << 63; @@ -318,9 +283,9 @@ private bool TryReadVarInt64(out ulong value) /// Reads a single byte from the stream, throwing if end is reached. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte ReadByteOrThrow() + private byte ReadByte() { - int byteValue = _stream.ReadByte(); + var byteValue = _stream.ReadByte(); if (byteValue < 0) { throw new EndOfStreamException("Unexpected end of stream"); @@ -332,18 +297,9 @@ private byte ReadByteOrThrow() /// /// Reads exactly the specified number of bytes into a span. /// - private void ReadExactBytes(Span buffer) + private void ReadBytes(Span buffer) { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - - int totalRead = 0; - while (totalRead < buffer.Length) - { - int read = _stream.Read(buffer.Slice(totalRead)); - if (read == 0) - throw new EndOfStreamException($"Unexpected end of stream. Expected {buffer.Length} bytes, but got {totalRead}"); - totalRead += read; - } + _stream.ReadExactly(buffer); } /// @@ -351,11 +307,9 @@ private void ReadExactBytes(Span buffer) /// private void SkipBytes(int count) { - ObjectDisposedException.ThrowIf(_disposed, nameof(PbfStreamReader)); - if (_stream.CanSeek) { - long newPosition = _stream.Position + count; + var newPosition = _stream.Position + count; if (newPosition > _stream.Length) { throw new EndOfStreamException($"Unexpected end of stream while skipping {count} bytes"); @@ -370,13 +324,10 @@ private void SkipBytes(int count) while (remaining > 0) { - int toRead = Math.Min(buffer.Length, remaining); - int read = _stream.Read(buffer.Slice(0, toRead)); - if (read == 0) - { - throw new EndOfStreamException($"Unexpected end of stream while skipping {count} bytes"); - } - remaining -= read; + var toRead = Math.Min(buffer.Length, remaining); + _stream.ReadExactly(buffer.Slice(0, toRead)); + + remaining -= toRead; } } @@ -385,10 +336,6 @@ private void SkipBytes(int count) /// public void Dispose() { - if (!_disposed) - { - _stream?.Dispose(); - _disposed = true; - } + _stream?.Dispose(); } } From 85ef362b8a262684e42a109f29e0b49eeb7ca53c Mon Sep 17 00:00:00 2001 From: Lukas Kabrt Date: Sat, 10 Jan 2026 18:21:44 +0100 Subject: [PATCH 3/8] Tests refactoring --- .../PbfBlockReaderTests.Primitives.cs | 82 ++------- .../PbfBlockReaderTests.SystemTypes.cs | 106 +++-------- src/PbfLite.Tests/PbfBlockReaderTests.cs | 66 +------ src/PbfLite.Tests/PbfEncodingTests.cs | 32 ++++ src/PbfLite.Tests/PbfReaderPrimitivesTests.cs | 89 +++++++++ .../PbfReaderSystemTypesTests.cs | 145 +++++++++++++++ src/PbfLite.Tests/PbfReaderTests.cs | 42 +++++ .../PbfStreamReaderTests.Primitives.cs | 91 +++------- .../PbfStreamReaderTests.SystemTypes.cs | 169 +++++------------- src/PbfLite.Tests/PbfStreamReaderTests.cs | 75 +------- 10 files changed, 421 insertions(+), 476 deletions(-) create mode 100644 src/PbfLite.Tests/PbfEncodingTests.cs create mode 100644 src/PbfLite.Tests/PbfReaderPrimitivesTests.cs create mode 100644 src/PbfLite.Tests/PbfReaderSystemTypesTests.cs create mode 100644 src/PbfLite.Tests/PbfReaderTests.cs 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 7cb9ee2..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 = 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/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/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.Primitives.cs b/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs index ad5459a..84564bb 100644 --- a/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs +++ b/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs @@ -4,89 +4,38 @@ namespace PbfLite.Tests; -public class PbfStreamReaderTests_Primitives +public partial class PbfStreamReaderTests { - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0U)] - [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 1U)] - [InlineData(new byte[] { 0xFF, 0x00, 0x00, 0x00 }, 255U)] - [InlineData(new byte[] { 0x00, 0x01, 0x00, 0x00 }, 256U)] - [InlineData(new byte[] { 0x00, 0x00, 0x01, 0x00 }, 65536U)] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x01 }, 16777216U)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, 4294967295U)] - public void ReadFixed32_ReadsNumbers(byte[] data, uint expectedNumber) + public class Primitives : PbfReaderPrimitivesTests { - using (var stream = new MemoryStream(data)) + public override uint ReadFixed32(byte[] data) { - var reader = new PbfStreamReader(stream); - - var number = reader.ReadFixed32(); - - Assert.Equal(expectedNumber, number); + var reader = new PbfStreamReader(new MemoryStream(data)); + return reader.ReadFixed32(); } - } - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0UL)] - [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 1UL)] - [InlineData(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 255UL)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }, 4294967295UL)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, 18446744073709551615UL)] - public void ReadFixed64_ReadsNumbers(byte[] data, ulong expectedNumber) - { - using (var stream = new MemoryStream(data)) + public override ulong ReadFixed64(byte[] data) { - var reader = new PbfStreamReader(stream); - - var number = reader.ReadFixed64(); - - Assert.Equal(expectedNumber, number); + var reader = new PbfStreamReader(new MemoryStream(data)); + return reader.ReadFixed64(); } - } - [Theory] - [InlineData(new byte[] { 0x00 }, 0U)] - [InlineData(new byte[] { 0x01 }, 1U)] - [InlineData(new byte[] { 0x7F }, 127U)] - [InlineData(new byte[] { 0x80, 0x01 }, 128U)] - [InlineData(new byte[] { 0x80, 0x80, 0x01 }, 16384U)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x01 }, 2097152U)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, 268435456U)] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, 4294967295U)] - public void ReadVarint32_ReadsNumbers(byte[] data, uint expectedNumber) - { - using (var stream = new MemoryStream(data)) + public override ReadOnlySpan ReadLengthPrefixedBytes(byte[] data) { - var reader = new PbfStreamReader(stream); - - var number = reader.ReadVarInt32(); - - Assert.Equal(expectedNumber, number); + var reader = new PbfStreamReader(new MemoryStream(data)); + return reader.ReadLengthPrefixedBytes(); } - } - [Theory] - [InlineData(new byte[] { 0x00 }, 0UL)] - [InlineData(new byte[] { 0x01 }, 1UL)] - [InlineData(new byte[] { 0x7F }, 127UL)] - [InlineData(new byte[] { 0x80, 0x01 }, 128UL)] - [InlineData(new byte[] { 0x80, 0x80, 0x01 }, 16384UL)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x01 }, 2097152UL)] - [InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x01 }, 268435456UL)] - [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) - { - using (var stream = new MemoryStream(data)) + public override uint ReadVarInt32(byte[] data) { - var reader = new PbfStreamReader(stream); - - var number = reader.ReadVarInt64(); + var reader = new PbfStreamReader(new MemoryStream(data)); + return reader.ReadVarInt32(); + } - Assert.Equal(expectedNumber, number); + public override ulong ReadVarInt64(byte[] data) + { + var reader = new PbfStreamReader(new MemoryStream(data)); + 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 index 27e5a9e..16c4c85 100644 --- a/src/PbfLite.Tests/PbfStreamReaderTests.SystemTypes.cs +++ b/src/PbfLite.Tests/PbfStreamReaderTests.SystemTypes.cs @@ -4,173 +4,88 @@ namespace PbfLite.Tests; -public class PbfStreamReaderTests_SystemTypes +public partial class PbfStreamReaderTests { - [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 class SystemTypes : PbfReaderSystemTypesTests { - using (var stream = new MemoryStream(data)) + public override string ReadString(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - 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) - { - using (var stream = new MemoryStream(data)) + public override bool ReadBoolean(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - - 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) - { - using (var stream = new MemoryStream(data)) + public override int ReadSignedInt(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - - 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) - { - using (var stream = new MemoryStream(data)) + public override int ReadInt(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - - var number = reader.ReadInt(); - - Assert.Equal(expectedNumber, number); + + return reader.ReadInt(); } - } - [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) - { - using (var stream = new MemoryStream(data)) + public override uint ReadUint(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - - var number = reader.ReadUint(); - - Assert.Equal(expectedNumber, number); + + return reader.ReadUint(); } - } - [Theory] - [InlineData(new byte[] { 0x00 }, 0L)] - [InlineData(new byte[] { 0x01 }, -1L)] - [InlineData(new byte[] { 0x02 }, 1L)] - [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) - { - using (var stream = new MemoryStream(data)) + public override long ReadSignedLong(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - - var number = reader.ReadSignedLong(); - - Assert.Equal(expectedNumber, number); + + return reader.ReadSignedLong(); } - } - [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) - { - using (var stream = new MemoryStream(data)) + public override long ReadLong(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - - var number = reader.ReadLong(); - - Assert.Equal(expectedNumber, number); + + return reader.ReadLong(); } - } - [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) - { - using (var stream = new MemoryStream(data)) + public override ulong ReadULong(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - - var number = reader.ReadULong(); - - Assert.Equal(expectedNumber, number); + + return reader.ReadULong(); } - } - [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) - { - using (var stream = new MemoryStream(data)) + public override float ReadSingle(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - - var number = reader.ReadSingle(); - - Assert.Equal(expectedNumber, number); + + return reader.ReadSingle(); } - } - [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) - { - using (var stream = new MemoryStream(data)) + public override double ReadDouble(byte[] data) { + using var stream = new MemoryStream(data); var reader = new PbfStreamReader(stream); - - var number = reader.ReadDouble(); - - Assert.Equal(expectedNumber, number); + + return reader.ReadDouble(); } } -} +} \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.cs b/src/PbfLite.Tests/PbfStreamReaderTests.cs index 8c52f69..cb3e89e 100644 --- a/src/PbfLite.Tests/PbfStreamReaderTests.cs +++ b/src/PbfLite.Tests/PbfStreamReaderTests.cs @@ -4,53 +4,14 @@ namespace PbfLite.Tests; -public class PbfStreamReaderTests +public partial class PbfStreamReaderTests : 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) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - - 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) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - - var header = reader.ReadFieldHeader(); - - Assert.Equal(wireType, header.wireType); - } - } - - [Fact] - public void ReadFieldHeader_ReturnsNoneWhenEndOfStreamIsReached() + public override (int fieldNumber, WireType wireType) ReadFieldHeader(byte[] data) { - using (var stream = new MemoryStream(Array.Empty())) - { - var reader = new PbfStreamReader(stream); - - var header = reader.ReadFieldHeader(); + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); - Assert.Equal(0, header.fieldNumber); - Assert.Equal(WireType.None, header.wireType); - } + return reader.ReadFieldHeader(); } [Theory] @@ -72,32 +33,6 @@ public void SkipFields_SkipsCorrectNumberOfBytes(byte[] data, WireType wireType, } } - [Theory] - [InlineData(0, 0)] - [InlineData(1, -1)] - [InlineData(2, 1)] - [InlineData(4294967294U, 2147483647)] - [InlineData(4294967295U, -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); - } - [Fact] public void ReadVarInt32_ReadsMinimalBytes() { From dc005259d25e0be96c747b732e53239a54fe4af9 Mon Sep 17 00:00:00 2001 From: Lukas Kabrt Date: Sat, 10 Jan 2026 18:41:32 +0100 Subject: [PATCH 4/8] Change read collections signature --- ...bfBlockReaderStreamReaderRoundTripTests.cs | 4 +- .../PbfStreamReaderTests.Collections.cs | 54 ++++++++++------ src/PbfLite/PbfStreamReader.Collections.cs | 61 +++++++++---------- 3 files changed, 68 insertions(+), 51 deletions(-) diff --git a/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs b/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs index a30b772..f2e63ad 100644 --- a/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs +++ b/src/PbfLite.Tests/PbfBlockReaderStreamReaderRoundTripTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Xunit; @@ -133,7 +134,8 @@ public void StreamReader_WithCollections_MatchesBlockReader() using (var stream = new MemoryStream(writer.Block.ToArray())) { var streamReader = new PbfStreamReader(stream); - var streamItems = streamReader.ReadUIntCollection(WireType.String); + var streamItems = new List(); + streamReader.ReadUIntCollection(WireType.String, streamItems); // Verify both readers get the same collection data Assert.Equal(blockItems.Length, streamItems.Count); diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs b/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs index ee58268..8700ab6 100644 --- a/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs +++ b/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs @@ -17,8 +17,9 @@ public void ReadUIntCollection_ReadsDataSerializedAsSingleElement(byte[] data, u using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadUIntCollection(WireType.VarInt); + reader.ReadUIntCollection(WireType.VarInt, items); Assert.Equal(expectedItems, items); } @@ -35,8 +36,9 @@ public void ReadUIntCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadUIntCollection(WireType.String); + reader.ReadUIntCollection(WireType.String, items); Assert.Equal(expectedItems, items); } @@ -51,8 +53,9 @@ public void ReadULongCollection_ReadsDataSerializedAsSingleElement(byte[] data, using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadULongCollection(WireType.VarInt); + reader.ReadULongCollection(WireType.VarInt, items); Assert.Equal(expectedItems, items); } @@ -66,8 +69,9 @@ public void ReadULongCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadULongCollection(WireType.String); + reader.ReadULongCollection(WireType.String, items); Assert.Equal(expectedItems, items); } @@ -82,8 +86,9 @@ public void ReadIntCollection_ReadsDataSerializedAsSingleElement(byte[] data, in using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadIntCollection(WireType.VarInt); + reader.ReadIntCollection(WireType.VarInt, items); Assert.Equal(expectedItems, items); } @@ -98,8 +103,9 @@ public void ReadIntCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, i using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadIntCollection(WireType.String); + reader.ReadIntCollection(WireType.String, items); Assert.Equal(expectedItems, items); } @@ -114,8 +120,9 @@ public void ReadLongCollection_ReadsDataSerializedAsSingleElement(byte[] data, l using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadLongCollection(WireType.VarInt); + reader.ReadLongCollection(WireType.VarInt, items); Assert.Equal(expectedItems, items); } @@ -129,8 +136,9 @@ public void ReadLongCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadLongCollection(WireType.String); + reader.ReadLongCollection(WireType.String, items); Assert.Equal(expectedItems, items); } @@ -145,8 +153,9 @@ public void ReadSignedIntCollection_ReadsDataSerializedAsSingleElement(byte[] da using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadSignedIntCollection(WireType.VarInt); + reader.ReadSignedIntCollection(WireType.VarInt, items); Assert.Equal(expectedItems, items); } @@ -161,8 +170,9 @@ public void ReadSignedIntCollection_ReadsDataSerializedAsLengthPrefixed(byte[] d using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadSignedIntCollection(WireType.String); + reader.ReadSignedIntCollection(WireType.String, items); Assert.Equal(expectedItems, items); } @@ -177,8 +187,9 @@ public void ReadSignedLongCollection_ReadsDataSerializedAsSingleElement(byte[] d using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadSignedLongCollection(WireType.VarInt); + reader.ReadSignedLongCollection(WireType.VarInt, items); Assert.Equal(expectedItems, items); } @@ -193,8 +204,9 @@ public void ReadSignedLongCollection_ReadsDataSerializedAsLengthPrefixed(byte[] using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadSignedLongCollection(WireType.String); + reader.ReadSignedLongCollection(WireType.String, items); Assert.Equal(expectedItems, items); } @@ -208,8 +220,9 @@ public void ReadBooleanCollection_ReadsDataSerializedAsSingleElement(byte[] data using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadBooleanCollection(WireType.VarInt); + reader.ReadBooleanCollection(WireType.VarInt, items); Assert.Equal(expectedItems, items); } @@ -223,8 +236,9 @@ public void ReadBooleanCollection_ReadsDataSerializedAsLengthPrefixed(byte[] dat using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadBooleanCollection(WireType.String); + reader.ReadBooleanCollection(WireType.String, items); Assert.Equal(expectedItems, items); } @@ -238,8 +252,9 @@ public void ReadSingleCollection_ReadsDataSerializedAsSingleElement(byte[] data, using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadSingleCollection(WireType.Fixed32); + reader.ReadSingleCollection(WireType.Fixed32, items); Assert.Equal(expectedItems, items); } @@ -253,8 +268,9 @@ public void ReadSingleCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadSingleCollection(WireType.String); + reader.ReadSingleCollection(WireType.String, items); Assert.Equal(expectedItems, items); } @@ -268,8 +284,9 @@ public void ReadDoubleCollection_ReadsDataSerializedAsSingleElement(byte[] data, using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadDoubleCollection(WireType.Fixed64); + reader.ReadDoubleCollection(WireType.Fixed64, items); Assert.Equal(expectedItems, items); } @@ -283,8 +300,9 @@ public void ReadDoubleCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data using (var stream = new MemoryStream(data)) { var reader = new PbfStreamReader(stream); + var items = new List(); - var items = reader.ReadDoubleCollection(WireType.String); + reader.ReadDoubleCollection(WireType.String, items); Assert.Equal(expectedItems, items); } diff --git a/src/PbfLite/PbfStreamReader.Collections.cs b/src/PbfLite/PbfStreamReader.Collections.cs index 8d66319..c781f34 100644 --- a/src/PbfLite/PbfStreamReader.Collections.cs +++ b/src/PbfLite/PbfStreamReader.Collections.cs @@ -7,10 +7,9 @@ public partial class PbfStreamReader { private delegate T ItemReaderDelegate(PbfStreamReader reader); - private List ReadScalarCollection(WireType wireType, WireType itemWireType, ItemReaderDelegate itemReader) + + private void ReadScalarCollection(WireType wireType, WireType itemWireType, List collection, ItemReaderDelegate itemReader) { - var collection = new List(); - if (wireType == WireType.String) { var byteLength = ReadVarInt32(); @@ -35,8 +34,6 @@ private List ReadScalarCollection(WireType wireType, WireType itemWireType { throw new InvalidOperationException($"Cannot read collection with wire type {wireType}"); } - - return collection; } private static readonly ItemReaderDelegate ReadUintDelegate = static (reader) => reader.ReadUint(); @@ -53,71 +50,71 @@ private List ReadScalarCollection(WireType wireType, WireType itemWireType /// Reads a collection of unsigned 32-bit integers from the stream. /// /// The wire type used to encode the collection. - /// A list of decoded unsigned 32-bit integers. - public List ReadUIntCollection(WireType wireType) => - ReadScalarCollection(wireType, WireType.VarInt, ReadUintDelegate); + /// 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. - /// A list of decoded unsigned 64-bit integers. - public List ReadULongCollection(WireType wireType) => - ReadScalarCollection(wireType, WireType.VarInt, ReadULongDelegate); + /// 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. - /// A list of decoded signed 32-bit integers. - public List ReadIntCollection(WireType wireType) => - ReadScalarCollection(wireType, WireType.VarInt, ReadIntDelegate); + /// 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. - /// A list of decoded signed 64-bit integers. - public List ReadLongCollection(WireType wireType) => - ReadScalarCollection(wireType, WireType.VarInt, ReadLongDelegate); + /// 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. - /// A list of decoded zigzag-encoded signed 32-bit integers. - public List ReadSignedIntCollection(WireType wireType) => - ReadScalarCollection(wireType, WireType.VarInt, ReadSignedIntDelegate); + /// 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. - /// A list of decoded zigzag-encoded signed 64-bit integers. - public List ReadSignedLongCollection(WireType wireType) => - ReadScalarCollection(wireType, WireType.VarInt, ReadSignedLongDelegate); + /// 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. - /// A list of decoded boolean values. - public List ReadBooleanCollection(WireType wireType) => - ReadScalarCollection(wireType, WireType.VarInt, ReadBooleanDelegate); + /// 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. - /// A list of decoded 32-bit floats. - public List ReadSingleCollection(WireType wireType) => - ReadScalarCollection(wireType, WireType.Fixed32, ReadSingleDelegate); + /// 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. - /// A list of decoded 64-bit floats. - public List ReadDoubleCollection(WireType wireType) => - ReadScalarCollection(wireType, WireType.Fixed64, ReadDoubleDelegate); + /// The collection to fill with decoded items. + public void ReadDoubleCollection(WireType wireType, List collection) => + ReadScalarCollection(wireType, WireType.Fixed64, collection, ReadDoubleDelegate); } From 8994c775202ff172f96f692ef6479ad438196dc2 Mon Sep 17 00:00:00 2001 From: Lukas Kabrt Date: Sat, 10 Jan 2026 20:18:12 +0100 Subject: [PATCH 5/8] Tests refactoring --- .../PbfBlockReaderTests.Collections.cs | 348 +++++------------- .../PbfReaderCollectionsTests.cs | 240 ++++++++++++ .../PbfStreamReaderTests.Collections.cs | 314 ++-------------- 3 files changed, 351 insertions(+), 551 deletions(-) create mode 100644 src/PbfLite.Tests/PbfReaderCollectionsTests.cs diff --git a/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs b/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs index 0fb835f..db29539 100644 --- a/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs +++ b/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs @@ -1,365 +1,185 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -using Xunit; namespace PbfLite.Tests; public partial class PbfBlockReaderTests { - public class CollectionsToBuffer : Collections + public class CollectionsToBuffer : PbfReaderCollectionsTests { - 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]); - } - - public class CollectionsToList : Collections - { - 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) => + protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) { - var list = new List(itemCount); - reader.ReadULongCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - }; + var buffer = new uint[itemCount]; - 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); + reader.ReadUIntCollection(wireType, buffer); - var items = UIntCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + return buffer; } - - [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) + protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) { - var buffer = new uint[expectedItems.Length]; - var reader = PbfBlockReader.Create(data); + var buffer = new ulong[itemCount]; - var items = UIntCollectionReader(ref reader, WireType.String, expectedItems.Length); + var reader = PbfBlockReader.Create(data); + reader.ReadULongCollection(wireType, buffer); - SpanAssert.Equal(expectedItems.AsSpan(), items); + return buffer; } - [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) + protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) { - var reader = PbfBlockReader.Create(data); + var buffer = new int[itemCount]; - var items = ULongCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); + var reader = PbfBlockReader.Create(data); + reader.ReadIntCollection(wireType, buffer); - SpanAssert.Equal(expectedItems.AsSpan(), items); + 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) + protected override ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount) { - var reader = PbfBlockReader.Create(data); + var buffer = new int[itemCount]; - var items = ULongCollectionReader(ref reader, WireType.String, expectedItems.Length); + var reader = PbfBlockReader.Create(data); + reader.ReadSignedIntCollection(wireType, buffer); - SpanAssert.Equal(expectedItems.AsSpan(), items); + return 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) + protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) { - var reader = PbfBlockReader.Create(data); + var buffer = new long[itemCount]; - var items = IntCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); + var reader = PbfBlockReader.Create(data); + reader.ReadLongCollection(wireType, buffer); - SpanAssert.Equal(expectedItems.AsSpan(), items); + return buffer; } - [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) + protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) { - var reader = PbfBlockReader.Create(data); + var buffer = new long[itemCount]; - var items = IntCollectionReader(ref reader, WireType.String, expectedItems.Length); + var reader = PbfBlockReader.Create(data); + reader.ReadSignedLongCollection(wireType, buffer); - SpanAssert.Equal(expectedItems.AsSpan(), items); + return buffer; } - [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) + protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) { - var reader = PbfBlockReader.Create(data); + var buffer = new bool[itemCount]; - var items = SignedIntCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); + var reader = PbfBlockReader.Create(data); + reader.ReadBooleanCollection(wireType, buffer); - SpanAssert.Equal(expectedItems.AsSpan(), items); + return buffer; } - [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) + protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) { - var reader = PbfBlockReader.Create(data); + var buffer = new float[itemCount]; - var items = SignedIntCollectionReader(ref reader, WireType.String, expectedItems.Length); + var reader = PbfBlockReader.Create(data); + reader.ReadSingleCollection(wireType, buffer); - SpanAssert.Equal(expectedItems.AsSpan(), items); + return buffer; } - [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) + protected override ReadOnlySpan ReadDoubleCollection(byte[] data, WireType wireType, int itemCount) { - var reader = PbfBlockReader.Create(data); + var buffer = new double[itemCount]; - var items = LongCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); + var reader = PbfBlockReader.Create(data); + reader.ReadDoubleCollection(wireType, buffer); - SpanAssert.Equal(expectedItems.AsSpan(), items); + return buffer; } + } - [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) + public class CollectionsToList : PbfReaderCollectionsTests + { + protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) { var reader = PbfBlockReader.Create(data); + var list = new List(itemCount); - var items = LongCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + reader.ReadUIntCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); } - [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) + protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) { var reader = PbfBlockReader.Create(data); + var list = new List(itemCount); - var items = SignedLongCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + reader.ReadULongCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); } - [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) + protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) { var reader = PbfBlockReader.Create(data); + var list = new List(itemCount); - var items = SignedLongCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + reader.ReadIntCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); } - [Theory] - [InlineData(new byte[] { 0x00 }, new bool[] { false })] - [InlineData(new byte[] { 0x01 }, new bool[] { true })] - public void ReadBooleanCollection_Buffer_ReadsDataSerializedAsSingleElement(byte[] data, bool[] expectedItems) + protected override ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount) { var reader = PbfBlockReader.Create(data); + var list = new List(itemCount); - var items = BooleanCollectionReader(ref reader, WireType.VarInt, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + reader.ReadSignedIntCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); } - [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) + protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) { var reader = PbfBlockReader.Create(data); + var list = new List(itemCount); - var items = BooleanCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + reader.ReadLongCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); } - [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) + protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) { var reader = PbfBlockReader.Create(data); + var list = new List(itemCount); - var items = SingleCollectionReader(ref reader, WireType.Fixed32, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + reader.ReadSignedLongCollection(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) + protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) { var reader = PbfBlockReader.Create(data); + var list = new List(itemCount); - var items = SingleCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + reader.ReadBooleanCollection(wireType, list); + return CollectionsMarshal.AsSpan(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) + protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) { var reader = PbfBlockReader.Create(data); + var list = new List(itemCount); - var items = DoubleCollectionReader(ref reader, WireType.Fixed64, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + reader.ReadSingleCollection(wireType, list); + return CollectionsMarshal.AsSpan(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) + protected override ReadOnlySpan ReadDoubleCollection(byte[] data, WireType wireType, int itemCount) { var reader = PbfBlockReader.Create(data); + var list = new List(itemCount); - var items = DoubleCollectionReader(ref reader, WireType.String, expectedItems.Length); - - SpanAssert.Equal(expectedItems.AsSpan(), items); + reader.ReadDoubleCollection(wireType, list); + return CollectionsMarshal.AsSpan(list); } } } \ 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/PbfStreamReaderTests.Collections.cs b/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs index 8700ab6..689e462 100644 --- a/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs +++ b/src/PbfLite.Tests/PbfStreamReaderTests.Collections.cs @@ -1,310 +1,50 @@ using System; using System.Collections.Generic; using System.IO; -using Xunit; +using System.Runtime.InteropServices; namespace PbfLite.Tests; -public class PbfStreamReaderTests_Collections +public partial class PbfStreamReaderTests { - [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_ReadsDataSerializedAsSingleElement(byte[] data, uint[] expectedItems) + public class Collections : PbfReaderCollectionsTests { - using (var stream = new MemoryStream(data)) + 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(); + var items = new List(itemCount); - reader.ReadUIntCollection(WireType.VarInt, items); + readAction(reader, wireType, items); - Assert.Equal(expectedItems, items); + return CollectionsMarshal.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_ReadsDataSerializedAsLengthPrefixed(byte[] data, uint[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadUIntCollection(WireType.String, items); - - Assert.Equal(expectedItems, 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_ReadsDataSerializedAsSingleElement(byte[] data, ulong[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadULongCollection(WireType.VarInt, items); - - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new ulong[] { 0 })] - [InlineData(new byte[] { 0x01, 0x01 }, new ulong[] { 1 })] - public void ReadULongCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, ulong[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadULongCollection(WireType.String, items); - - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x00 }, new int[] { 0 })] - [InlineData(new byte[] { 0x01 }, new int[] { 1 })] - [InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -1 })] - public void ReadIntCollection_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadIntCollection(WireType.VarInt, items); - - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new int[] { 0 })] - [InlineData(new byte[] { 0x01, 0x01 }, new int[] { 1 })] - [InlineData(new byte[] { 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, new int[] { -1 })] - public void ReadIntCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadIntCollection(WireType.String, items); - - Assert.Equal(expectedItems, 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 })] - public void ReadLongCollection_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadLongCollection(WireType.VarInt, items); - - Assert.Equal(expectedItems, items); - } - } - [Theory] - [InlineData(new byte[] { 0x01, 0x00 }, new long[] { 0 })] - [InlineData(new byte[] { 0x01, 0x01 }, new long[] { 1 })] - public void ReadLongCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); + protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadUIntCollection(wireType, items)); - reader.ReadLongCollection(WireType.String, items); + protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadULongCollection(wireType, items)); - Assert.Equal(expectedItems, items); - } - } + protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadIntCollection(wireType, items)); - [Theory] - [InlineData(new byte[] { 0x00 }, new int[] { 0 })] - [InlineData(new byte[] { 0x01 }, new int[] { -1 })] - [InlineData(new byte[] { 0x02 }, new int[] { 1 })] - public void ReadSignedIntCollection_ReadsDataSerializedAsSingleElement(byte[] data, int[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); + protected override ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadSignedIntCollection(wireType, items)); - reader.ReadSignedIntCollection(WireType.VarInt, items); + protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadLongCollection(wireType, items)); - Assert.Equal(expectedItems, items); - } - } + protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadSignedLongCollection(wireType, 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 })] - public void ReadSignedIntCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, int[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); + protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadBooleanCollection(wireType, items)); - reader.ReadSignedIntCollection(WireType.String, items); + protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, items) => reader.ReadSingleCollection(wireType, items)); - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x00 }, new long[] { 0 })] - [InlineData(new byte[] { 0x01 }, new long[] { -1 })] - [InlineData(new byte[] { 0x02 }, new long[] { 1 })] - public void ReadSignedLongCollection_ReadsDataSerializedAsSingleElement(byte[] data, long[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadSignedLongCollection(WireType.VarInt, items); - - Assert.Equal(expectedItems, 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 })] - public void ReadSignedLongCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, long[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadSignedLongCollection(WireType.String, items); - - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x00 }, new bool[] { false })] - [InlineData(new byte[] { 0x01 }, new bool[] { true })] - public void ReadBooleanCollection_ReadsDataSerializedAsSingleElement(byte[] data, bool[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadBooleanCollection(WireType.VarInt, items); - - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x02, 0x00, 0x01 }, new bool[] { false, true })] - [InlineData(new byte[] { 0x04, 0x01, 0x00, 0x01, 0x00 }, new bool[] { true, false, true, false })] - public void ReadBooleanCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, bool[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadBooleanCollection(WireType.String, items); - - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0f })] - [InlineData(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 3.14f })] - public void ReadSingleCollection_ReadsDataSerializedAsSingleElement(byte[] data, float[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadSingleCollection(WireType.Fixed32, items); - - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x04, 0x00, 0x00, 0x00, 0x00 }, new float[] { 0f })] - [InlineData(new byte[] { 0x08, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xF5, 0x48, 0x40 }, new float[] { 0f, 3.14f })] - public void ReadSingleCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, float[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadSingleCollection(WireType.String, items); - - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0d })] - [InlineData(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 3.14 })] - public void ReadDoubleCollection_ReadsDataSerializedAsSingleElement(byte[] data, double[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadDoubleCollection(WireType.Fixed64, items); - - Assert.Equal(expectedItems, items); - } - } - - [Theory] - [InlineData(new byte[] { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0d })] - [InlineData(new byte[] { 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }, new double[] { 0d, 3.14 })] - public void ReadDoubleCollection_ReadsDataSerializedAsLengthPrefixed(byte[] data, double[] expectedItems) - { - using (var stream = new MemoryStream(data)) - { - var reader = new PbfStreamReader(stream); - var items = new List(); - - reader.ReadDoubleCollection(WireType.String, items); - - Assert.Equal(expectedItems, 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 From 51000a150ac044211b82b8f7d91813c4cf62c58f Mon Sep 17 00:00:00 2001 From: Lukas Kabrt Date: Sat, 10 Jan 2026 20:34:24 +0100 Subject: [PATCH 6/8] Tests refactoring --- .../PbfBlockReaderTests.Collections.cs | 187 +++++------------- src/PbfLite.Tests/PbfStreamReaderTests.cs | 55 ++---- src/PbfLite/PbfStreamReader.cs | 8 - 3 files changed, 59 insertions(+), 191 deletions(-) diff --git a/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs b/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs index db29539..ce5f461 100644 --- a/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs +++ b/src/PbfLite.Tests/PbfBlockReaderTests.Collections.cs @@ -6,180 +6,81 @@ namespace PbfLite.Tests; public partial class PbfBlockReaderTests { + private delegate void ReadCollectionDelegate(PbfBlockReader reader, WireType wireType, T collection); + public class CollectionsToBuffer : PbfReaderCollectionsTests { - protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) + private static ReadOnlySpan ReadCollection(byte[] data, WireType wireType, int itemCount, ReadCollectionDelegate readAction) { - var buffer = new uint[itemCount]; - + var buffer = new T[itemCount]; var reader = PbfBlockReader.Create(data); - reader.ReadUIntCollection(wireType, buffer); - + readAction(reader, wireType, buffer); return buffer; } - protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) - { - var buffer = new ulong[itemCount]; - - var reader = PbfBlockReader.Create(data); - reader.ReadULongCollection(wireType, buffer); + protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadUIntCollection(wireType, buffer)); - return buffer; - } + protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadULongCollection(wireType, buffer)); - protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) - { - var buffer = new int[itemCount]; + protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadIntCollection(wireType, buffer)); - var reader = PbfBlockReader.Create(data); - 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)); - return buffer; - } - - protected override ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount) - { - var buffer = new int[itemCount]; - - var reader = PbfBlockReader.Create(data); - reader.ReadSignedIntCollection(wireType, buffer); - - return buffer; - } - - protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) - { - var buffer = new long[itemCount]; - - var reader = PbfBlockReader.Create(data); - reader.ReadLongCollection(wireType, buffer); + protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadLongCollection(wireType, buffer)); - return buffer; - } - - protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) - { - var buffer = new long[itemCount]; - - var reader = PbfBlockReader.Create(data); - reader.ReadSignedLongCollection(wireType, buffer); - - return buffer; - } - - protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) - { - var buffer = new bool[itemCount]; - - var reader = PbfBlockReader.Create(data); - reader.ReadBooleanCollection(wireType, buffer); - - return buffer; - } - - protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) - { - var buffer = new float[itemCount]; - - var reader = PbfBlockReader.Create(data); - reader.ReadSingleCollection(wireType, buffer); - - return buffer; - } + protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadSignedLongCollection(wireType, buffer)); - protected override ReadOnlySpan ReadDoubleCollection(byte[] data, WireType wireType, int itemCount) - { - var buffer = new double[itemCount]; + protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadBooleanCollection(wireType, buffer)); - var reader = PbfBlockReader.Create(data); - reader.ReadDoubleCollection(wireType, buffer); + protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadSingleCollection(wireType, buffer)); - return buffer; - } + protected override ReadOnlySpan ReadDoubleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, buffer) => reader.ReadDoubleCollection(wireType, buffer)); } public class CollectionsToList : PbfReaderCollectionsTests { - protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) + private static ReadOnlySpan ReadCollection(byte[] data, WireType wireType, int itemCount, ReadCollectionDelegate> readAction) { var reader = PbfBlockReader.Create(data); - var list = new List(itemCount); - - reader.ReadUIntCollection(wireType, list); + var list = new List(itemCount); + readAction(reader, wireType, list); return CollectionsMarshal.AsSpan(list); } - protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) - { - var reader = PbfBlockReader.Create(data); - var list = new List(itemCount); + protected override ReadOnlySpan ReadUIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadUIntCollection(wireType, list)); - reader.ReadULongCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - } + protected override ReadOnlySpan ReadULongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadULongCollection(wireType, list)); - protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) - { - var reader = PbfBlockReader.Create(data); - var list = new List(itemCount); + protected override ReadOnlySpan ReadIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadIntCollection(wireType, list)); - reader.ReadIntCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - } + protected override ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadSignedIntCollection(wireType, list)); - protected override ReadOnlySpan ReadSignedIntCollection(byte[] data, WireType wireType, int itemCount) - { - var reader = PbfBlockReader.Create(data); - var list = new List(itemCount); + protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadLongCollection(wireType, list)); - reader.ReadSignedIntCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - } + protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadSignedLongCollection(wireType, list)); - protected override ReadOnlySpan ReadLongCollection(byte[] data, WireType wireType, int itemCount) - { - var reader = PbfBlockReader.Create(data); - var list = new List(itemCount); + protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadBooleanCollection(wireType, list)); - reader.ReadLongCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - } + protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) => + ReadCollection(data, wireType, itemCount, (reader, wireType, list) => reader.ReadSingleCollection(wireType, list)); - protected override ReadOnlySpan ReadSignedLongCollection(byte[] data, WireType wireType, int itemCount) - { - var reader = PbfBlockReader.Create(data); - var list = new List(itemCount); - - reader.ReadSignedLongCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - } - - protected override ReadOnlySpan ReadBooleanCollection(byte[] data, WireType wireType, int itemCount) - { - var reader = PbfBlockReader.Create(data); - var list = new List(itemCount); - - reader.ReadBooleanCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - } - - protected override ReadOnlySpan ReadSingleCollection(byte[] data, WireType wireType, int itemCount) - { - var reader = PbfBlockReader.Create(data); - var list = new List(itemCount); - - reader.ReadSingleCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - } - - protected override ReadOnlySpan ReadDoubleCollection(byte[] data, WireType wireType, int itemCount) - { - var reader = PbfBlockReader.Create(data); - var list = new List(itemCount); - - reader.ReadDoubleCollection(wireType, list); - return CollectionsMarshal.AsSpan(list); - } + 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/PbfStreamReaderTests.cs b/src/PbfLite.Tests/PbfStreamReaderTests.cs index cb3e89e..08a34dd 100644 --- a/src/PbfLite.Tests/PbfStreamReaderTests.cs +++ b/src/PbfLite.Tests/PbfStreamReaderTests.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using Xunit; @@ -39,13 +38,13 @@ 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 } @@ -57,13 +56,13 @@ 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 } @@ -73,13 +72,13 @@ public void ReadVarInt64_ReadsMinimalBytes() 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 } @@ -89,13 +88,13 @@ public void ReadFixed32_ReadsExactly4Bytes() 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 } @@ -106,39 +105,15 @@ 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 } } - - [Fact] - public void Dispose_ClosesStream() - { - var stream = new MemoryStream(new byte[] { 0x00 }); - var reader = new PbfStreamReader(stream); - - reader.Dispose(); - - // Stream should be closed - Assert.Throws(() => stream.WriteByte(0)); - } - - [Fact] - public void ThrowsAfterDispose() - { - var stream = new MemoryStream(new byte[] { 0x00 }); - var reader = new PbfStreamReader(stream); - - reader.Dispose(); - - // Operations should throw ObjectDisposedException - Assert.Throws(() => reader.ReadFieldHeader()); - } } diff --git a/src/PbfLite/PbfStreamReader.cs b/src/PbfLite/PbfStreamReader.cs index a64b239..93c2273 100644 --- a/src/PbfLite/PbfStreamReader.cs +++ b/src/PbfLite/PbfStreamReader.cs @@ -330,12 +330,4 @@ private void SkipBytes(int count) remaining -= toRead; } } - - /// - /// Closes the stream and releases resources. - /// - public void Dispose() - { - _stream?.Dispose(); - } } From 1d007c7141a4345530a6d670b17dc6cc259caffe Mon Sep 17 00:00:00 2001 From: Lukas Kabrt Date: Sat, 10 Jan 2026 20:37:35 +0100 Subject: [PATCH 7/8] Update version number --- src/PbfLite/PbfLite.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 0560c3befc826acb29af2e59f94ef73095c7774d Mon Sep 17 00:00:00 2001 From: Lukas Kabrt Date: Sat, 10 Jan 2026 20:56:35 +0100 Subject: [PATCH 8/8] PR suggestions --- .../PbfStreamReaderTests.Primitives.cs | 15 ++++++++++----- src/PbfLite/PbfStreamReader.Collections.cs | 1 - src/PbfLite/PbfStreamReader.SystemTypes.cs | 4 +--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs b/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs index 84564bb..0251b5c 100644 --- a/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs +++ b/src/PbfLite.Tests/PbfStreamReaderTests.Primitives.cs @@ -10,31 +10,36 @@ public class Primitives : PbfReaderPrimitivesTests { public override uint ReadFixed32(byte[] data) { - var reader = new PbfStreamReader(new MemoryStream(data)); + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); return reader.ReadFixed32(); } public override ulong ReadFixed64(byte[] data) { - var reader = new PbfStreamReader(new MemoryStream(data)); + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); return reader.ReadFixed64(); } public override ReadOnlySpan ReadLengthPrefixedBytes(byte[] data) { - var reader = new PbfStreamReader(new MemoryStream(data)); + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); return reader.ReadLengthPrefixedBytes(); } public override uint ReadVarInt32(byte[] data) { - var reader = new PbfStreamReader(new MemoryStream(data)); + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); return reader.ReadVarInt32(); } public override ulong ReadVarInt64(byte[] data) { - var reader = new PbfStreamReader(new MemoryStream(data)); + using var stream = new MemoryStream(data); + var reader = new PbfStreamReader(stream); return reader.ReadVarInt64(); } } diff --git a/src/PbfLite/PbfStreamReader.Collections.cs b/src/PbfLite/PbfStreamReader.Collections.cs index c781f34..403a5cc 100644 --- a/src/PbfLite/PbfStreamReader.Collections.cs +++ b/src/PbfLite/PbfStreamReader.Collections.cs @@ -7,7 +7,6 @@ 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) diff --git a/src/PbfLite/PbfStreamReader.SystemTypes.cs b/src/PbfLite/PbfStreamReader.SystemTypes.cs index 96800b5..0eab2a4 100644 --- a/src/PbfLite/PbfStreamReader.SystemTypes.cs +++ b/src/PbfLite/PbfStreamReader.SystemTypes.cs @@ -6,8 +6,6 @@ namespace PbfLite; public partial class PbfStreamReader { - private static readonly Encoding encoding = Encoding.UTF8; - /// /// Reads a UTF-8 encoded length-prefixed string from the stream. /// @@ -16,7 +14,7 @@ public partial class PbfStreamReader public string ReadString() { var buffer = ReadLengthPrefixedBytes(); - return encoding.GetString(buffer); + return Encoding.UTF8.GetString(buffer); } ///