Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/PbfLite.Tests/PbfBlockWriterTests.Primitives.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using Xunit;

namespace PbfLite.Tests;
Expand Down Expand Up @@ -97,5 +98,41 @@ public void WriteLengthPrefixedBytes_WritesPrefixAndData()

SpanAssert.Equal<byte>(expected, writer.Block);
}

[Theory]
[InlineData(1)]
[InlineData(128)]
[InlineData(32768)]
public void StartAndFinalizeLengthPrefixedBlock_WritesLengthPrefixedBlock(int estimatedBlockLength)
{
var data = Enumerable.Repeat<byte>(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<byte>(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<byte>(data, writer.Block);
Assert.Equal(5, writer.Position);
}
}
}
1 change: 1 addition & 0 deletions src/PbfLite.Tests/PbfBlockWriterTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using Xunit;

namespace PbfLite.Tests;
Expand Down
22 changes: 3 additions & 19 deletions src/PbfLite/PbfBlockWriter.Collections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,17 @@ namespace PbfLite;
public ref partial struct PbfBlockWriter
{
private delegate void ItemWriterDelegate<T>(ref PbfBlockWriter writer, T item);

private void WriteScalarCollection<T>(ReadOnlySpan<T> items, ItemWriterDelegate<T> 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<T>(ReadOnlySpan<T> items, ItemWriterDelegate<T> itemWriter, int contentLengthBytes)
Expand Down
54 changes: 53 additions & 1 deletion src/PbfLite/PbfBlockWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,56 @@ public void WriteLengthPrefixedBytes(ReadOnlySpan<byte> data)
data.CopyTo(_block[_position..]);
_position += data.Length;
}
}

/// <summary>
/// Writes the specified sequence of bytes directly to the underlying buffer at the current position.
/// </summary>
/// <param name="data">The span of bytes to write to the buffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteRaw(ReadOnlySpan<byte> data)
{
data.CopyTo(_block[_position..]);
_position += data.Length;
}

/// <summary>
/// Starts a length-prefixed block with an estimated block length.
/// </summary>
/// <param name="estimatedBlockLength">The estimated length of the block content.</param>
/// <returns>A LengthPrefixedBlock struct representing the block.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public LengthPrefixedBlock StartLengthPrefixedBlock(int estimatedBlockLength)
{
var lengthPosition = _position;
var contentPosition = lengthPosition + GetVarIntBytesCount((uint)estimatedBlockLength);
_position = contentPosition;

return new LengthPrefixedBlock(lengthPosition, contentPosition);
}

/// <summary>
/// Finalizes a length-prefixed block by calculating its actual length and writing it at the appropriate position.
/// </summary>
/// <param name="block">The LengthPrefixedBlock to finalize.</param>
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);
2 changes: 1 addition & 1 deletion src/PbfLite/PbfLite.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<!-- NuGet Package Metadata -->
<PackageId>PbfLite</PackageId>
<Version>0.3.0</Version>
<Version>0.4.0</Version>
<Authors>Lukas Kabrt</Authors>
<Description>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.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
Loading