Skip to content

Commit 1c8f472

Browse files
committed
Use IBufferWriter instead of byte[]
- Return value is now `ReadOnlyMemory<byte>` - Add overloads for reading `ReadOnlyMemory<byte>` - Add `FluentAssertions` extensions
1 parent 6dc21f4 commit 1c8f472

File tree

5 files changed

+65
-29
lines changed

5 files changed

+65
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
2+
// SPDX-License-Identifier: LGPL-3.0-only
3+
4+
using FluentAssertions;
5+
using FluentAssertions.Collections;
6+
7+
namespace Nethermind.Serialization.FluentRlp.Test;
8+
9+
// NOTE: `FluentAssertions` currently does not support `(ReadOnly)Span<T>` or `(ReadOnly)Memory<T>` assertions.
10+
public static class Extensions
11+
{
12+
public static GenericCollectionAssertions<T> Should<T>(this ReadOnlySpan<T> span) => span.ToArray().Should();
13+
public static GenericCollectionAssertions<T> Should<T>(this ReadOnlyMemory<T> memory) => memory.ToArray().Should();
14+
15+
public static AndConstraint<GenericCollectionAssertions<TExpectation>> BeEquivalentTo<TExpectation>(
16+
this GenericCollectionAssertions<TExpectation> @this,
17+
ReadOnlySpan<TExpectation> expectation,
18+
string because = "",
19+
params object[] becauseArgs)
20+
{
21+
return @this.BeEquivalentTo(expectation.ToArray(), config => config, because, becauseArgs);
22+
}
23+
24+
public static AndConstraint<GenericCollectionAssertions<TExpectation>> BeEquivalentTo<TExpectation>(
25+
this GenericCollectionAssertions<TExpectation> @this,
26+
ReadOnlyMemory<TExpectation> expectation,
27+
string because = "",
28+
params object[] becauseArgs)
29+
{
30+
return @this.BeEquivalentTo(expectation.ToArray(), config => config, because, becauseArgs);
31+
}
32+
}

src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpDerivedTest.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class RlpDerivedTest
4343
public void FlatRecord()
4444
{
4545
var player = new Player(Id: 42, Username: "SuperUser");
46-
ReadOnlySpan<byte> rlp = Rlp.Write(player, static (ref RlpWriter w, Player player) => w.Write(player));
46+
var rlp = Rlp.Write(player, static (ref RlpWriter w, Player player) => w.Write(player));
4747

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

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

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

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

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

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

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

140140
var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadAccessList());
141141
decoded.Should().BeEquivalentTo(accessList);

src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpReadWriteTest.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,10 @@ public void Choice()
201201
{
202202
RefRlpReaderFunc<int> intReader = static (scoped ref RlpReader r) => r.ReadInt32();
203203
RefRlpReaderFunc<int> wrappedReader = (scoped ref RlpReader r) => r.ReadSequence(intReader);
204-
205204
var intRlp = Rlp.Write(static (ref RlpWriter w) => { w.Write(42); });
206205
var wrappedIntRlp = Rlp.Write(static (ref RlpWriter w) => w.WriteSequence(static (ref RlpWriter w) => { w.Write(42); }));
207206

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

src/Nethermind/Nethermind.Serialization.FluentRlp/Rlp.cs

+11-5
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,32 @@
22
// SPDX-License-Identifier: LGPL-3.0-only
33

44
using System;
5+
using System.Buffers;
6+
using System.Runtime.CompilerServices;
57

68
namespace Nethermind.Serialization.FluentRlp;
79

810
public static class Rlp
911
{
10-
public static byte[] Write(RefRlpWriterAction action)
12+
public static ReadOnlyMemory<byte> Write(RefRlpWriterAction action)
1113
=> Write(action, static (ref RlpWriter w, RefRlpWriterAction action) => action(ref w));
1214

13-
public static byte[] Write<TContext>(TContext ctx, RefRlpWriterAction<TContext> action)
15+
public static ReadOnlyMemory<byte> Write<TContext>(TContext ctx, RefRlpWriterAction<TContext> action)
1416
where TContext : allows ref struct
1517
{
1618
var lengthWriter = RlpWriter.LengthWriter();
1719
action(ref lengthWriter, ctx);
18-
var serialized = new byte[lengthWriter.Length];
19-
var contentWriter = RlpWriter.ContentWriter(serialized);
20+
var buffer = new ArrayBufferWriter<byte>(lengthWriter.Length);
21+
var contentWriter = RlpWriter.ContentWriter(buffer);
2022
action(ref contentWriter, ctx);
2123

22-
return serialized;
24+
return buffer.WrittenMemory;
2325
}
2426

27+
public static T Read<T>(ReadOnlyMemory<byte> source, RefRlpReaderFunc<T> func) where T : allows ref struct
28+
=> Read(source.Span, func);
29+
30+
[OverloadResolutionPriority(1)]
2531
public static T Read<T>(ReadOnlySpan<byte> source, RefRlpReaderFunc<T> func)
2632
where T : allows ref struct
2733
{

src/Nethermind/Nethermind.Serialization.FluentRlp/RlpWriter.cs

+14-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: LGPL-3.0-only
33

44
using System;
5+
using System.Buffers;
56
using System.Buffers.Binary;
67
using System.Numerics;
78

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

2223
public int Length { get; private set; }
2324

24-
private byte[] _buffer;
25-
private int _position;
25+
private IBufferWriter<byte> _buffer;
2626

2727
public static RlpWriter LengthWriter()
2828
{
@@ -32,7 +32,7 @@ public static RlpWriter LengthWriter()
3232
};
3333
}
3434

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

8585
if (bigEndian.Length == 0)
8686
{
87-
_buffer[_position++] = 0x80;
87+
_buffer.Write([(byte)0x80]);
8888
}
8989
else if (bigEndian.Length == 1 && bigEndian[0] < 0x80)
9090
{
91-
_buffer[_position++] = bigEndian[0];
91+
_buffer.Write(bigEndian[..1]);
9292
}
9393
else
9494
{
@@ -130,20 +130,19 @@ private void ContentWrite(scoped ReadOnlySpan<byte> value)
130130
{
131131
if (value.Length < 55)
132132
{
133-
_buffer[_position++] = (byte)(0x80 + value.Length);
133+
_buffer.Write([(byte)(0x80 + value.Length)]);
134134
}
135135
else
136136
{
137137
Span<byte> binaryLength = stackalloc byte[sizeof(int)];
138138
BinaryPrimitives.WriteInt32BigEndian(binaryLength, value.Length);
139139
binaryLength = binaryLength.TrimStart((byte)0);
140-
_buffer[_position++] = (byte)(0xB7 + binaryLength.Length);
141-
binaryLength.CopyTo(_buffer.AsSpan()[_position..]);
142-
_position += binaryLength.Length;
140+
141+
_buffer.Write([(byte)(0xB7 + binaryLength.Length)]);
142+
_buffer.Write(binaryLength);
143143
}
144144

145-
value.CopyTo(_buffer.AsSpan()[_position..]);
146-
_position += value.Length;
145+
_buffer.Write(value);
147146
}
148147

149148
public void WriteSequence(RefRlpWriterAction action)
@@ -185,16 +184,16 @@ private void ContentWriteSequence<TContext>(TContext ctx, RefRlpWriterAction<TCo
185184
action(ref lengthWriter, ctx);
186185
if (lengthWriter.Length < 55)
187186
{
188-
_buffer[_position++] = (byte)(0xC0 + lengthWriter.Length);
187+
_buffer.Write([(byte)(0xC0 + lengthWriter.Length)]);
189188
}
190189
else
191190
{
192191
Span<byte> binaryLength = stackalloc byte[sizeof(Int32)];
193192
BinaryPrimitives.WriteInt32BigEndian(binaryLength, lengthWriter.Length);
194193
binaryLength = binaryLength.TrimStart((byte)0);
195-
_buffer[_position++] = (byte)(0xF7 + binaryLength.Length);
196-
binaryLength.CopyTo(_buffer.AsSpan()[_position..]);
197-
_position += binaryLength.Length;
194+
195+
_buffer.Write([(byte)(0xF7 + binaryLength.Length)]);
196+
_buffer.Write(binaryLength);
198197
}
199198

200199
action(ref this, ctx);

0 commit comments

Comments
 (0)