diff --git a/src/lib.zig b/src/lib.zig index 47f484a..9e7dfd9 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -210,6 +210,28 @@ pub fn serialize(comptime T: type, data: T, l: *ArrayList(u8)) !void { } } +pub fn deserialize_slice(comptime T: type, serialized: []const u8, out: []T) !void { + if (try isFixedSizeObject(T)) { + var i: usize = 0; + const pitch = @sizeOf(T); + while (i < out.len) : (i += pitch) { + try deserialize(T, serialized[i * pitch .. (i + 1) * pitch], &out[i]); + } + } else { + const size = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little) / @sizeOf(u32); + const indices = std.mem.bytesAsSlice(u32, serialized[0 .. size * 4]); + var i = @as(usize, 0); + while (i < size) : (i += 1) { + const end = if (i < size - 1) indices[i + 1] else serialized.len; + const start = indices[i]; + if (start >= serialized.len or end > serialized.len) { + return error.IndexOutOfBounds; + } + try deserialize(T, serialized[start..end], &out[i]); + } + } +} + /// Takes a byte array containing the serialized payload of type `T` (with /// possible trailing data) and deserializes it into the `T` object pointed /// at by `out`. @@ -278,25 +300,7 @@ pub fn deserialize(comptime T: type, serialized: []const u8, out: *T) !void { // the responsibility of the caller. out.* = serialized[0..]; } else { - if (try isFixedSizeObject(ptr.child)) { - comptime var i = 0; - const pitch = @sizeOf(ptr.child); - inline while (i < out.len) : (i += pitch) { - try deserialize(ptr.child, serialized[i * pitch .. (i + 1) * pitch], &out[i]); - } - } else { - const size = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little) / @sizeOf(u32); - const indices = std.mem.bytesAsSlice(u32, serialized[0 .. size * 4]); - var i = @as(usize, 0); - while (i < size) : (i += 1) { - const end = if (i < size - 1) indices[i + 1] else serialized.len; - const start = indices[i]; - if (start >= serialized.len or end > serialized.len) { - return error.IndexOutOfBounds; - } - try deserialize(ptr.child, serialized[start..end], &out[i]); - } - } + try deserialize_slice(ptr.child, serialized, out.*); }, .One => return deserialize(ptr.child, serialized, out.*), else => return error.UnSupportedPointerType, @@ -342,10 +346,15 @@ pub fn deserialize(comptime T: type, serialized: []const u8, out: *T) !void { inline for (info.Struct.fields) |field| { // comptime fields are currently not supported, and it's not even // certain that they can ever be without a change in the language. - if (field.is_comptime) @panic("structure contains comptime field"); - switch (@typeInfo(field.type)) { + const tinfo = @typeInfo(field.type); + switch (tinfo) { .Bool, .Int => {}, // covered by the previous pass + .Pointer => |ptr| switch (ptr.size) { + .One => try deserialize(ptr.child, serialized, @field(out, field.name)), + .Slice => try deserialize_slice(ptr.child, serialized, @field(out.*, field.name)), + else => return error.UnsupportedPointerType, + }, else => { const end = if (last_index == indices.len - 1) serialized.len else indices[last_index + 1]; try deserialize(field.type, serialized[indices[last_index]..end], &@field(out.*, field.name)); diff --git a/src/tests.zig b/src/tests.zig index b037853..d1206b3 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -143,17 +143,36 @@ test "serializes a structure without variable fields" { try expect(std.mem.eql(u8, list.items, serialized_data[0..])); } -test "(de)serializes a structure with variable fields" { +test "(de)serializes a runtime structure with variable-size fields" { // Taken from ssz.cr const Person = struct { - name: []const u8, + name: []u8, age: u8, - company: []const u8, + company: []u8, }; var data = Person{ - .name = "James", + .name = try std.testing.allocator.alloc(u8, 5), + .age = 32, + .company = try std.testing.allocator.alloc(u8, 8), + }; + @memcpy(data.name, "James"); + @memcpy(data.company, "DEV Inc."); + const serialized_data = [_]u8{ 9, 0, 0, 0, 32, 14, 0, 0, 0, 74, 97, 109, 101, 115, 68, 69, 86, 32, 73, 110, 99, 46 }; + + var list = ArrayList(u8).init(std.testing.allocator); + defer list.deinit(); + // Note the `&data` - this is so that `data` is not considered const. + try serialize(@TypeOf(&data), &data, &list); + try expect(std.mem.eql(u8, list.items, serialized_data[0..])); + var out: @TypeOf(data) = undefined; + try deserialize(@TypeOf(data), list.items, &out); +} + +test "(de)serializes a comptime structure with variable-size fields" { + // Taken from ssz.cr + var data = .{ .age = 32, - .company = "DEV Inc.", + .company = @as([]u8, @constCast("DEV Inc.")), }; const serialized_data = [_]u8{ 9, 0, 0, 0, 32, 14, 0, 0, 0, 74, 97, 109, 101, 115, 68, 69, 86, 32, 73, 110, 99, 46 }; @@ -347,17 +366,17 @@ test "deserializes a string" { } const Pastry = struct { - name: []const u8, + name: []u8, weight: u16, }; const pastries = [_]Pastry{ Pastry{ - .name = "croissant", + .name = @as([]u8, @constCast("croissant")), .weight = 20, }, Pastry{ - .name = "Herrentorte", + .name = @as([]u8, @constCast("Herrentorte")), .weight = 500, }, };