Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/deserialize.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ inline fn safeReadSliceIntBig(comptime T: type, payload: []const u8, out: *T) !v

// Returns the size of the payload as well as the offset to the
// start of the actual data.
fn sizeAndDataOffset(payload: []const u8) !struct { size: usize, offset: usize } {
pub fn sizeAndDataOffset(payload: []const u8) !struct { size: usize, offset: usize } {
var size: usize = undefined;
var offset: usize = undefined;

Expand Down
108 changes: 56 additions & 52 deletions src/serialize.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator;
const hasFn = std.meta.hasFn;

pub const deserialize = @import("deserialize.zig").deserialize;
const sizeAndDataOffset = @import("deserialize.zig").sizeAndDataOffset;

fn writeLengthLength(length: usize, list: *ArrayList(u8)) !u8 {
var enc_length_buf: [8]u8 = undefined;
Expand Down Expand Up @@ -173,19 +174,27 @@ pub fn serialize(comptime T: type, allocator: Allocator, data: T, list: *ArrayLi
}

pub const RawRLPValue = union(enum) {
value: []const u8,
list: []const RawRLPValue,
value: []const u8, // already-encoded RLP; emitted verbatim
list: []const RawRLPValue, // children wrapped with an RLP list header

pub fn encodeToRLP(self: RawRLPValue, allocator: Allocator, list: *ArrayList(u8)) !void {
return switch (self) {
.value => |v| {
try serialize([]const u8, allocator, v, list);
try list.appendSlice(v);
},
.list => |v| {
try serialize([]const RawRLPValue, allocator, v, list);
},
};
}

pub fn decodeFromRLP(self: *RawRLPValue, _: Allocator, serialized: []const u8) !usize {
const r = try sizeAndDataOffset(serialized);
const consumed = r.offset + r.size;
if (consumed > serialized.len) return error.RlpPayloadTooShort;
self.* = .{ .value = serialized[0..consumed] };
return consumed;
}
};

test "serialize an integer" {
Expand Down Expand Up @@ -411,55 +420,50 @@ test "one byte slicei with value > 128" {
try std.testing.expectEqualSlices(u8, &[_]u8{ 0x81, 0xff }, out.items);
}

test "raw rlp" {
test "raw rlp value is verbatim, list wraps" {
const allocator = std.testing.allocator;
{
var out_generic = ArrayList(u8).init(allocator);
defer out_generic.deinit();
const generic: RawRLPValue = .{ .value = "hello" };
try serialize(RawRLPValue, allocator, generic, &out_generic);

var out = ArrayList(u8).init(allocator);
defer out.deinit();
const normal = "hello";
try serialize([]const u8, allocator, normal, &out);

try std.testing.expectEqualSlices(u8, out.items, out_generic.items);
}
{
var out_generic = ArrayList(u8).init(allocator);
defer out_generic.deinit();
const generic: RawRLPValue = .{ .list = &[_]RawRLPValue{ .{ .value = "hello" }, .{ .value = "world" } } };
try serialize(RawRLPValue, allocator, generic, &out_generic);

var out = ArrayList(u8).init(allocator);
defer out.deinit();
var normal = [_][]const u8{ "hello", "world" };
try serialize([]const []const u8, allocator, &normal, &out);

try std.testing.expectEqualSlices(u8, out.items, out_generic.items);
}
{
var out_generic = ArrayList(u8).init(allocator);
defer out_generic.deinit();
const generic: RawRLPValue = .{
.list = &[_]RawRLPValue{
.{ .value = "hello" },
.{ .value = "world" },
.{ .list = &[_]RawRLPValue{.{ .value = "nested" }} },
},
};
try serialize(RawRLPValue, allocator, generic, &out_generic);

var out = ArrayList(u8).init(allocator);
defer out.deinit();
const normal: struct { a: []const u8, b: []const u8, c: []const []const u8 } = .{
.a = "hello",
.b = "world",
.c = &[_][]const u8{"nested"},
};
try serialize(@TypeOf(normal), allocator, normal, &out);
var out = ArrayList(u8).init(allocator);
defer out.deinit();

try std.testing.expectEqualSlices(u8, out.items, out_generic.items);
}
// .value holds already-encoded RLP and is emitted as-is.
try serialize(RawRLPValue, allocator, RawRLPValue{ .value = &([_]u8{0x85} ++ "hello".*) }, &out);
try testing.expectEqualSlices(u8, &([_]u8{0x85} ++ "hello".*), out.items);

// .list wraps its raw children with a freshly computed list header.
out.clearRetainingCapacity();
try serialize(RawRLPValue, allocator, RawRLPValue{ .list = &[_]RawRLPValue{
.{ .value = &([_]u8{0x85} ++ "hello".*) },
.{ .value = &([_]u8{0x85} ++ "world".*) },
} }, &out);
try testing.expectEqualSlices(u8, &([_]u8{0xcc} ++ [_]u8{0x85} ++ "hello".* ++ [_]u8{0x85} ++ "world".*), out.items);
}

test "raw rlp captures items verbatim" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const a = arena.allocator();

// [ "hello", "world", [ "nested" ] ] in canonical RLP.
const encoded = [_]u8{0xd4} ++
[_]u8{0x85} ++ "hello".* ++
[_]u8{0x85} ++ "world".* ++
[_]u8{ 0xc7, 0x86 } ++ "nested".*;

// As a single value: the whole item is captured verbatim (not split).
var whole: RawRLPValue = undefined;
try testing.expectEqual(encoded.len, try deserialize(RawRLPValue, a, &encoded, &whole));
try testing.expectEqualSlices(u8, &encoded, whole.value);

// As a slice: the list is split one level, each element captured verbatim
// (the nested list is NOT descended into).
var items: []RawRLPValue = undefined;
try testing.expectEqual(encoded.len, try deserialize([]RawRLPValue, a, &encoded, &items));
try testing.expectEqual(@as(usize, 3), items.len);
try testing.expectEqualSlices(u8, &([_]u8{0x85} ++ "hello".*), items[0].value);
try testing.expectEqualSlices(u8, &([_]u8{ 0xc7, 0x86 } ++ "nested".*), items[2].value);

// Re-encoding the elements reproduces the original list.
var reencoded = ArrayList(u8).init(a);
try serialize([]const RawRLPValue, a, items, &reencoded);
try testing.expectEqualSlices(u8, &encoded, reencoded.items);
}
Loading