diff --git a/src/PbfLite.Tests/PbfBlockWriterTests.Primitives.cs b/src/PbfLite.Tests/PbfBlockWriterTests.Primitives.cs index 65dfed5..7f904a3 100644 --- a/src/PbfLite.Tests/PbfBlockWriterTests.Primitives.cs +++ b/src/PbfLite.Tests/PbfBlockWriterTests.Primitives.cs @@ -1,3 +1,4 @@ +using System.Linq; using Xunit; namespace PbfLite.Tests; @@ -97,5 +98,41 @@ public void WriteLengthPrefixedBytes_WritesPrefixAndData() SpanAssert.Equal(expected, writer.Block); } + + [Theory] + [InlineData(1)] + [InlineData(128)] + [InlineData(32768)] + public void StartAndFinalizeLengthPrefixedBlock_WritesLengthPrefixedBlock(int estimatedBlockLength) + { + var data = Enumerable.Repeat(0x01, 128).ToArray(); + + var buffer = new byte[256]; + var writer = PbfBlockWriter.Create(buffer); + + var block = writer.StartLengthPrefixedBlock(estimatedBlockLength); + writer.WriteRaw(data); + writer.FinalizeLengthPrefixedBlock(block); + + var expectedData = new byte[2 + data.Length]; + expectedData[0] = 0x80; + expectedData[1] = 0x01; + data.CopyTo(expectedData, 2); + + SpanAssert.Equal(expectedData, writer.Block.Slice(0, expectedData.Length)); + } + + [Fact] + public void WriteRaw_WritesDataAndAdvancesPosition() + { + var data = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; + var buffer = new byte[10]; + var writer = PbfBlockWriter.Create(buffer); + + writer.WriteRaw(data); + + SpanAssert.Equal(data, writer.Block); + Assert.Equal(5, writer.Position); + } } } \ No newline at end of file diff --git a/src/PbfLite.Tests/PbfBlockWriterTests.cs b/src/PbfLite.Tests/PbfBlockWriterTests.cs index cd5f72b..21134a9 100644 --- a/src/PbfLite.Tests/PbfBlockWriterTests.cs +++ b/src/PbfLite.Tests/PbfBlockWriterTests.cs @@ -1,3 +1,4 @@ +using System.Linq; using Xunit; namespace PbfLite.Tests; diff --git a/src/PbfLite/PbfBlockWriter.Collections.cs b/src/PbfLite/PbfBlockWriter.Collections.cs index b794bc8..6bb0bf8 100644 --- a/src/PbfLite/PbfBlockWriter.Collections.cs +++ b/src/PbfLite/PbfBlockWriter.Collections.cs @@ -5,33 +5,17 @@ namespace PbfLite; public ref partial struct PbfBlockWriter { private delegate void ItemWriterDelegate(ref PbfBlockWriter writer, T item); - + private void WriteScalarCollection(ReadOnlySpan items, ItemWriterDelegate itemWriter) { - var lengthPosition = _position; - - // Placeholder for length that will be overwritten - WriteVarInt32(0); + var block = StartLengthPrefixedBlock(items.Length); foreach (var item in items) { itemWriter(ref this, item); } - var contentLength = _position - lengthPosition - 1; - var contentLengthBytesCount = GetVarIntBytesCount((uint)contentLength); - - if (contentLengthBytesCount > 1) - { - var content = _block.Slice(lengthPosition + 1, contentLength); - var newContentStart = lengthPosition + contentLengthBytesCount; - - content.CopyTo(_block.Slice(newContentStart)); - - _position = newContentStart + contentLength; - } - - WriteVarInt32At(lengthPosition, (uint)contentLength); + FinalizeLengthPrefixedBlock(block); } private void WriteScalarCollection(ReadOnlySpan items, ItemWriterDelegate itemWriter, int contentLengthBytes) diff --git a/src/PbfLite/PbfBlockWriter.cs b/src/PbfLite/PbfBlockWriter.cs index 41e6021..4bc70ef 100644 --- a/src/PbfLite/PbfBlockWriter.cs +++ b/src/PbfLite/PbfBlockWriter.cs @@ -139,4 +139,56 @@ public void WriteLengthPrefixedBytes(ReadOnlySpan data) data.CopyTo(_block[_position..]); _position += data.Length; } -} \ No newline at end of file + + /// + /// Writes the specified sequence of bytes directly to the underlying buffer at the current position. + /// + /// The span of bytes to write to the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteRaw(ReadOnlySpan data) + { + data.CopyTo(_block[_position..]); + _position += data.Length; + } + + /// + /// Starts a length-prefixed block with an estimated block length. + /// + /// The estimated length of the block content. + /// A LengthPrefixedBlock struct representing the block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LengthPrefixedBlock StartLengthPrefixedBlock(int estimatedBlockLength) + { + var lengthPosition = _position; + var contentPosition = lengthPosition + GetVarIntBytesCount((uint)estimatedBlockLength); + _position = contentPosition; + + return new LengthPrefixedBlock(lengthPosition, contentPosition); + } + + /// + /// Finalizes a length-prefixed block by calculating its actual length and writing it at the appropriate position. + /// + /// The LengthPrefixedBlock to finalize. + public void FinalizeLengthPrefixedBlock(LengthPrefixedBlock block) + { + var estimatedLengthBytesCount = block.ContentPosition - block.LengthPosition; + + var contentLength = _position - block.ContentPosition; + var contentLengthBytesCount = GetVarIntBytesCount((uint)contentLength); + + if (contentLengthBytesCount != estimatedLengthBytesCount) + { + var content = _block.Slice(block.ContentPosition, contentLength); + var newContentStart = block.LengthPosition + contentLengthBytesCount; + + content.CopyTo(_block.Slice(newContentStart)); + + _position = newContentStart + contentLength; + } + + WriteVarInt32At(block.LengthPosition, (uint)contentLength); + } +} + +public record struct LengthPrefixedBlock(int LengthPosition, int ContentPosition); \ No newline at end of file diff --git a/src/PbfLite/PbfLite.csproj b/src/PbfLite/PbfLite.csproj index b58657d..d709a5d 100644 --- a/src/PbfLite/PbfLite.csproj +++ b/src/PbfLite/PbfLite.csproj @@ -5,7 +5,7 @@ PbfLite - 0.3.0 + 0.4.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