Skip to content

Commit

Permalink
Use IBufferWriter instead of byte[]
Browse files Browse the repository at this point in the history
- Use `var` in return types
- Add overloads for `ReadOnlyMemory<byte>`
- Add `FluentAssertions` extensions for `Span`
  • Loading branch information
emlautarom1 committed Jan 6, 2025
1 parent 6dc21f4 commit dcd6d85
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using FluentAssertions;
using FluentAssertions.Collections;

namespace Nethermind.Serialization.FluentRlp.Test;

// NOTE: `FluentAssertions` currently does not support `(ReadOnly)Span<T>` assertions.
public static class Extensions
{
public static GenericCollectionAssertions<T> Should<T>(this ReadOnlySpan<T> span) => span.ToArray().Should();

public static AndConstraint<GenericCollectionAssertions<TExpectation>> BeEquivalentTo<TExpectation>(
this GenericCollectionAssertions<TExpectation> @this,
ReadOnlySpan<TExpectation> expectation,
string because = "",
params object[] becauseArgs)
{
return @this.BeEquivalentTo(expectation.ToArray(), config => config, because, becauseArgs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class RlpDerivedTest
public void FlatRecord()
{
var player = new Player(Id: 42, Username: "SuperUser");
ReadOnlySpan<byte> rlp = Rlp.Write(player, static (ref RlpWriter w, Player player) => w.Write(player));
var rlp = Rlp.Write(player, static (ref RlpWriter w, Player player) => w.Write(player));

var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadPlayer());
decoded.Should().BeEquivalentTo(player);
Expand All @@ -53,7 +53,7 @@ public void FlatRecord()
public void RecordWithList()
{
var player = new PlayerWithFriends(Id: 42, Username: "SuperUser", Friends: ["ana", "bob"]);
ReadOnlySpan<byte> rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithFriends player) => w.Write(player));
var rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithFriends player) => w.Write(player));

var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadPlayerWithFriends());
decoded.Should().BeEquivalentTo(player);
Expand All @@ -63,7 +63,7 @@ public void RecordWithList()
public void RecordWithArray()
{
var player = new PlayerWithCodes(Id: 42, Username: "SuperUser", Codes: [2, 4, 8, 16, 32, 64]);
ReadOnlySpan<byte> rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithCodes player) => w.Write(player));
var rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithCodes player) => w.Write(player));

var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadPlayerWithCodes());
decoded.Should().BeEquivalentTo(player);
Expand All @@ -77,7 +77,7 @@ public void RecordWithDictionary()
{ "foo", 42 },
{ "bar", 1337 }
});
ReadOnlySpan<byte> rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithScores player) => w.Write(player));
var rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithScores player) => w.Write(player));

var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadPlayerWithScores());
decoded.Should().BeEquivalentTo(player);
Expand All @@ -87,7 +87,7 @@ public void RecordWithDictionary()
public void RecordWithTuple()
{
var integerTuple = new IntegerTuple((42, 1337));
ReadOnlySpan<byte> rlp = Rlp.Write(integerTuple, static (ref RlpWriter w, IntegerTuple tuple) => w.Write(tuple));
var rlp = Rlp.Write(integerTuple, static (ref RlpWriter w, IntegerTuple tuple) => w.Write(tuple));

var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadIntegerTuple());
decoded.Should().BeEquivalentTo(integerTuple);
Expand All @@ -103,7 +103,7 @@ [new Tree("dog", [])]),
new Tree("qux",
[new Tree("cat", [])])
]);
ReadOnlySpan<byte> rlp = Rlp.Write(tree, static (ref RlpWriter w, Tree tree) => w.Write(tree));
var rlp = Rlp.Write(tree, static (ref RlpWriter w, Tree tree) => w.Write(tree));

var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadTree());
decoded.Should().BeEquivalentTo(tree);
Expand Down Expand Up @@ -135,7 +135,7 @@ public void RecordWithNestedGenerics()
(new Address("0xFEDCBA0987654321"), [2, 4, 6, 8, 10])
]);

ReadOnlySpan<byte> rlp = Rlp.Write(accessList, (ref RlpWriter writer, AccessList value) => writer.Write(value));
var rlp = Rlp.Write(accessList, (ref RlpWriter writer, AccessList value) => writer.Write(value));

var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadAccessList());
decoded.Should().BeEquivalentTo(accessList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,10 @@ public void Choice()
{
RefRlpReaderFunc<int> intReader = static (scoped ref RlpReader r) => r.ReadInt32();
RefRlpReaderFunc<int> wrappedReader = (scoped ref RlpReader r) => r.ReadSequence(intReader);

var intRlp = Rlp.Write(static (ref RlpWriter w) => { w.Write(42); });
var wrappedIntRlp = Rlp.Write(static (ref RlpWriter w) => w.WriteSequence(static (ref RlpWriter w) => { w.Write(42); }));

foreach (var rlp in (byte[][])[intRlp, wrappedIntRlp])
foreach (var rlp in (ReadOnlyMemory<byte>[])[intRlp, wrappedIntRlp])
{
int decoded = Rlp.Read(rlp, (scoped ref RlpReader r) => r.Choice(wrappedReader, intReader));

Expand Down
16 changes: 11 additions & 5 deletions src/Nethermind/Nethermind.Serialization.FluentRlp/Rlp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,32 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Buffers;
using System.Runtime.CompilerServices;

namespace Nethermind.Serialization.FluentRlp;

public static class Rlp
{
public static byte[] Write(RefRlpWriterAction action)
public static ReadOnlyMemory<byte> Write(RefRlpWriterAction action)
=> Write(action, static (ref RlpWriter w, RefRlpWriterAction action) => action(ref w));

public static byte[] Write<TContext>(TContext ctx, RefRlpWriterAction<TContext> action)
public static ReadOnlyMemory<byte> Write<TContext>(TContext ctx, RefRlpWriterAction<TContext> action)
where TContext : allows ref struct
{
var lengthWriter = RlpWriter.LengthWriter();
action(ref lengthWriter, ctx);
var serialized = new byte[lengthWriter.Length];
var contentWriter = RlpWriter.ContentWriter(serialized);
var buffer = new ArrayBufferWriter<byte>(lengthWriter.Length);
var contentWriter = RlpWriter.ContentWriter(buffer);
action(ref contentWriter, ctx);

return serialized;
return buffer.WrittenMemory;
}

public static T Read<T>(ReadOnlyMemory<byte> source, RefRlpReaderFunc<T> func) where T : allows ref struct
=> Read(source.Span, func);

[OverloadResolutionPriority(1)]
public static T Read<T>(ReadOnlySpan<byte> source, RefRlpReaderFunc<T> func)
where T : allows ref struct
{
Expand Down
29 changes: 14 additions & 15 deletions src/Nethermind/Nethermind.Serialization.FluentRlp/RlpWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Numerics;

Expand All @@ -21,8 +22,7 @@ public ref struct RlpWriter

public int Length { get; private set; }

private byte[] _buffer;
private int _position;
private IBufferWriter<byte> _buffer;

public static RlpWriter LengthWriter()
{
Expand All @@ -32,7 +32,7 @@ public static RlpWriter LengthWriter()
};
}

public static RlpWriter ContentWriter(byte[] buffer)
public static RlpWriter ContentWriter(IBufferWriter<byte> buffer)
{
return new RlpWriter
{
Expand Down Expand Up @@ -84,11 +84,11 @@ private unsafe void ContentWrite<T>(T value) where T : unmanaged, IBinaryInteger

if (bigEndian.Length == 0)
{
_buffer[_position++] = 0x80;
_buffer.Write([(byte)0x80]);
}
else if (bigEndian.Length == 1 && bigEndian[0] < 0x80)
{
_buffer[_position++] = bigEndian[0];
_buffer.Write(bigEndian[..1]);
}
else
{
Expand Down Expand Up @@ -130,20 +130,19 @@ private void ContentWrite(scoped ReadOnlySpan<byte> value)
{
if (value.Length < 55)
{
_buffer[_position++] = (byte)(0x80 + value.Length);
_buffer.Write([(byte)(0x80 + value.Length)]);
}
else
{
Span<byte> binaryLength = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32BigEndian(binaryLength, value.Length);
binaryLength = binaryLength.TrimStart((byte)0);
_buffer[_position++] = (byte)(0xB7 + binaryLength.Length);
binaryLength.CopyTo(_buffer.AsSpan()[_position..]);
_position += binaryLength.Length;

_buffer.Write([(byte)(0xB7 + binaryLength.Length)]);
_buffer.Write(binaryLength);
}

value.CopyTo(_buffer.AsSpan()[_position..]);
_position += value.Length;
_buffer.Write(value);
}

public void WriteSequence(RefRlpWriterAction action)
Expand Down Expand Up @@ -185,16 +184,16 @@ private void ContentWriteSequence<TContext>(TContext ctx, RefRlpWriterAction<TCo
action(ref lengthWriter, ctx);
if (lengthWriter.Length < 55)
{
_buffer[_position++] = (byte)(0xC0 + lengthWriter.Length);
_buffer.Write([(byte)(0xC0 + lengthWriter.Length)]);
}
else
{
Span<byte> binaryLength = stackalloc byte[sizeof(Int32)];
BinaryPrimitives.WriteInt32BigEndian(binaryLength, lengthWriter.Length);
binaryLength = binaryLength.TrimStart((byte)0);
_buffer[_position++] = (byte)(0xF7 + binaryLength.Length);
binaryLength.CopyTo(_buffer.AsSpan()[_position..]);
_position += binaryLength.Length;

_buffer.Write([(byte)(0xF7 + binaryLength.Length)]);
_buffer.Write(binaryLength);
}

action(ref this, ctx);
Expand Down

0 comments on commit dcd6d85

Please sign in to comment.