~alva/zig-bare

d48e6cd090483e22036ca932e119a8d0c6894fec — owl 6 months ago 9827be4
port unmarshal tests from go-bare

This uncovered bugs in zig-bare. More tests will be ported, and all bugs
fixed.
4 files changed, 107 insertions(+), 21 deletions(-)

M build.zig.zon
M src/bare.zig
A src/interop/go-bare/unmarshal_test.zig
M src/test.zig
M build.zig.zon => build.zig.zon +2 -2
@@ 4,8 4,8 @@
    .paths = .{""},
    .dependencies = .{
        .varint = .{
            .url = "https://git.sr.ht/~alva/zig-varint/archive/f9e5b7f.tar.gz",
            .hash = "1220136609b836df8ff707062b100395f49d6c87187c3e74312c223618ad69e9a9ab",
            .url = "https://git.sr.ht/~alva/zig-varint/archive/09078dd.tar.gz",
            .hash = "122043d671db521d5f4aa6ec2c34c5b9c7536f95704f585a9046004cbed652c23b63",
        },
    },
}

M src/bare.zig => src/bare.zig +31 -16
@@ 5,12 5,19 @@ const math = std.math;
const mem = std.mem;
const meta = std.meta;

pub const DecodeError = error{ Overflow, InvalidUnion };
pub const DecodeError = error{
    Overflow,
    InvalidBool,
    InvalidUnion,
};

pub fn decoder(allocator: mem.Allocator, reader: anytype) Decoder(@TypeOf(reader)) {
    return Decoder(@TypeOf(reader)).init(allocator, reader);
}

pub const VarInt = varint.VarInt;
pub const VarUint = varint.VarUint;

/// Decodes BARE messages.
/// Uses an internal arena backed by the passed-in allocator.
pub fn Decoder(comptime ReaderType: type) type {


@@ 40,6 47,10 @@ pub fn Decoder(comptime ReaderType: type) type {
                .Bool => self.decodeBool(),
                .Struct => if (comptime isHashMap(T))
                    self.decodeHashMap(T)
                else if (T == VarInt)
                    self.decodeVarInt()
                else if (T == VarUint)
                    self.decodeVarUint()
                else
                    self.decodeStruct(T),
                .Enum => self.decodeEnum(T),


@@ 58,11 69,11 @@ pub fn Decoder(comptime ReaderType: type) type {
            };
        }

        fn decodeVarInt(self: *Self) !i64 {
        fn decodeVarInt(self: *Self) !VarInt {
            return varint.decodeVarInt(self.reader);
        }

        fn decodeVarUint(self: *Self) !u64 {
        fn decodeVarUint(self: *Self) !VarUint {
            return varint.decodeVarUint(self.reader);
        }



@@ 82,7 93,11 @@ pub fn Decoder(comptime ReaderType: type) type {
        }

        fn decodeBool(self: *Self) !bool {
            return 0 != try self.reader.readByte();
            return switch (try self.reader.readByte()) {
                0 => false,
                1 => true,
                else => DecodeError.InvalidBool,
            };
        }

        fn decodeStruct(self: *Self, comptime T: type) !T {


@@ 99,7 114,7 @@ pub fn Decoder(comptime ReaderType: type) type {
        }

        fn decodeEnum(self: *Self, comptime T: type) !T {
            const x: meta.Tag(T) = @intCast(try self.decodeVarUint());
            const x: meta.Tag(T) = @intCast((try self.decodeVarUint())[0]);
            return meta.intToEnum(T, x);
        }



@@ 132,7 147,7 @@ pub fn Decoder(comptime ReaderType: type) type {
            const tag = try self.decodeVarUint();

            inline for (ti.fields) |f| {
                if (tag == @intFromEnum(@field(ti.tag_type.?, f.name))) {
                if (tag[0] == @intFromEnum(@field(ti.tag_type.?, f.name))) {
                    const v = try self.decodeAllowVoid(f.type);
                    return @unionInit(T, f.name, v);
                }


@@ 148,9 163,9 @@ pub fn Decoder(comptime ReaderType: type) type {
                @compileError("slices are the only supported pointer type");

            const len = try self.decodeVarUint();
            var buf = try self.arena.allocator().alloc(ti.child, len);
            var buf = try self.arena.allocator().alloc(ti.child, len[0]);

            for (0..len) |i|
            for (0..len[0]) |i|
                buf[i] = try self.decode(ti.child);

            return buf;


@@ 165,13 180,13 @@ pub fn Decoder(comptime ReaderType: type) type {

            const i = try self.decodeVarUint();

            if (std.math.maxInt(T.Size) < i)
            if (std.math.maxInt(T.Size) < i[0])
                return DecodeError.Overflow;

            var map = T.Unmanaged{};
            try map.ensureUnusedCapacity(self.arena.allocator(), @intCast(i));
            try map.ensureUnusedCapacity(self.arena.allocator(), @intCast(i[0]));

            for (0..i) |_| {
            for (0..i[0]) |_| {
                const key = try self.decode(K);
                const val = try self.decode(V);
                _ = map.putAssumeCapacity(key, val);


@@ 230,7 245,7 @@ pub fn Encoder(comptime WriterType: type) type {
            };
        }

        fn encodeVarUint(self: *Self, value: u64) !void {
        fn encodeVarUint(self: *Self, value: VarUint) !void {
            return varint.encodeVarUint(self.writer, value);
        }



@@ 270,7 285,7 @@ pub fn Encoder(comptime WriterType: type) type {
        }

        fn encodeEnum(self: *Self, value: anytype) !void {
            try self.encodeVarUint(@intFromEnum(value));
            try self.encodeVarUint(VarUint{@intFromEnum(value)});
        }

        fn encodeOptional(self: *Self, value: anytype) !void {


@@ 296,7 311,7 @@ pub fn Encoder(comptime WriterType: type) type {
            if (ti.size != .Slice)
                @compileError("slices are the only supported pointer type");

            try self.encodeVarUint(value.len);
            try self.encodeVarUint(VarUint{value.len});

            for (value) |v|
                try self.encode(v);


@@ 309,7 324,7 @@ pub fn Encoder(comptime WriterType: type) type {
            if (comptime !isValidHashMapKeyType(K))
                @compileError("unsupported hashmap key type " ++ @typeName(K));

            try self.encodeVarUint(value.count());
            try self.encodeVarUint(VarUint{value.count()});

            var it = value.iterator();



@@ 325,7 340,7 @@ pub fn Encoder(comptime WriterType: type) type {

            if (ti.tag_type) |TT| {
                const tag = @intFromEnum(value);
                try self.encodeVarUint(tag);
                try self.encodeVarUint(VarUint{tag});

                inline for (ti.fields) |f| {
                    if (value == @field(TT, f.name))

A src/interop/go-bare/unmarshal_test.zig => src/interop/go-bare/unmarshal_test.zig +70 -0
@@ 0,0 1,70 @@
const std = @import("std");
const io = std.io;
const math = std.math;
const mem = std.mem;
const testing = std.testing;

const bare = @import("bare");
const decoder = bare.decoder;
const encoder = bare.encoder;

const expect = testing.expect;
const expectEqual = testing.expectEqual;
const expectEqualSlices = testing.expectEqualSlices;
const expectError = testing.expectError;

const payloads1 = [_][]const u8{
    &[_]u8{0x42},
    &[_]u8{ 0xFE, 0xCA },
    &[_]u8{ 0xEF, 0xBE, 0xAD, 0xDE },
    &[_]u8{ 0xEF, 0xBE, 0xAD, 0xDE, 0xBE, 0xBA, 0xFE, 0xCA },
    &[_]u8{ 0xEF, 0xFD, 0xB6, 0xF5, 0x0D },
    &[_]u8{0xD6},
    &[_]u8{ 0x2E, 0xFB },
    &[_]u8{ 0xB2, 0x9E, 0x43, 0xFF },
    &[_]u8{ 0x4F, 0x0B, 0x6E, 0x9D, 0xAB, 0x23, 0xD4, 0xFF },
    &[_]u8{ 0x9B, 0x85, 0xE3, 0x0B },
    &[_]u8{ 0x71, 0x2D, 0xA7, 0x44 },
    &[_]u8{ 0x9B, 0x6C, 0xC9, 0x20, 0xF0, 0x21, 0x3F, 0x42 },
    &[_]u8{ 0x00, 0x01, 0x02 },
    &[_]u8{ 0x1B, 0xE3, 0x81, 0x93, 0xE3, 0x82, 0x93, 0xE3, 0x81, 0xAB, 0xE3, 0x81, 0xA1, 0xE3, 0x81, 0xAF, 0xE3, 0x80, 0x81, 0xE4, 0xB8, 0x96, 0xE7, 0x95, 0x8C, 0xEF, 0xBC, 0x81 },
};

// tests ported from go-bare
test "unmarshal value" {
    inline for (.{
        .{ payloads1[0], u8, 0x42 },
        .{ payloads1[1], u16, 0xCAFE },
        .{ payloads1[2], u32, 0xDEADBEEF },
        .{ payloads1[3], u64, 0xCAFEBABEDEADBEEF },
        //.{payloads1[4], c_uint, 0xDEADBEEF },
        .{ payloads1[4], bare.VarUint, bare.VarUint{0xDEADBEEF} },
        .{ payloads1[5], i8, -42 },
        .{ payloads1[6], i16, -1234 },
        .{ payloads1[7], i32, -12345678 },
        .{ payloads1[8], i64, -12345678987654321 },
        //.{payloads1[9], c_int, -12345678 },
        .{ payloads1[9], bare.VarInt, bare.VarInt{-12345678} },
        .{ payloads1[10], f32, 1337.42 },
        .{ payloads1[11], f64, 133713371337.42424242 },
        .{ payloads1[12][0..], bool, false },
        .{ payloads1[12][1..], bool, true },
        .{ payloads1[12][2..], bool, error.InvalidBool },
        .{ payloads1[13], []const u8, "こんにちは、世界!" },
    }) |tuple| {
        var fbs = io.fixedBufferStream(tuple[0]);
        const reader = fbs.reader();
        var d = decoder(testing.allocator, reader);
        defer d.deinit();
        const res = switch (@typeInfo(@TypeOf(tuple[2]))) {
            .ErrorSet => |_| {
                try testing.expectError(tuple[2], d.decode(tuple[1]));
                continue;
            },
            else => try d.decode(tuple[1]),
        };
        try testing.expectEqualDeep(@as(tuple[1], tuple[2]), res); // catch {
        //    try std.io.getStdErr().writer().print("{any}", .{tuple});
        //};
    }
}

M src/test.zig => src/test.zig +4 -3
@@ 15,6 15,7 @@ const expectError = testing.expectError;

test "refAllDecls" {
    std.testing.refAllDecls(bare);
    std.testing.refAllDecls(@import("interop/go-bare/unmarshal_test.zig"));
}

test "read u8" {


@@ 40,7 41,7 @@ test "read bool" {
    defer d.deinit();
    try expectEqual(d.decode(bool), false);
    fbs = io.fixedBufferStream("\xff");
    try expectEqual(d.decode(bool), true);
    try expectError(error.InvalidBool, d.decode(bool));
    fbs = io.fixedBufferStream("\x01");
    try expectEqual(d.decode(bool), true);
}


@@ 52,7 53,7 @@ test "read struct 1" {
        c: bool,
    };

    var fbs = io.fixedBufferStream("\x00\x00\x28\x42\x2a\xaa");
    var fbs = io.fixedBufferStream("\x00\x00\x28\x42\x2a\x01");
    const reader = fbs.reader();
    var d = decoder(testing.allocator, reader);
    defer d.deinit();


@@ 137,7 138,7 @@ test "read struct 6" {
        c: bool,
    };

    var fbs = io.fixedBufferStream("\xff\xff\xff\xff\xff\xff");
    var fbs = io.fixedBufferStream("\xff\xff\xff\xff\xff\x01");
    const reader = fbs.reader();
    var d = decoder(testing.allocator, reader);
    defer d.deinit();