949 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			Zig
		
	
	
	
	
	
			
		
		
	
	
			949 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			Zig
		
	
	
	
	
	
| const std = @import("std");
 | |
| 
 | |
| const eql = std.mem.eql;
 | |
| const bufPrint = std.fmt.bufPrint;
 | |
| const fixedBufferStream = std.io.fixedBufferStream;
 | |
| const maxInt = std.math.maxInt;
 | |
| const minInt = std.math.minInt;
 | |
| const json = std.json;
 | |
| const fba = std.heap.FixedBufferAllocator;
 | |
| 
 | |
| pub const CborError = error{
 | |
|     CborIntegerTooLarge,
 | |
|     CborIntegerTooSmall,
 | |
|     CborInvalidType,
 | |
|     CborTooShort,
 | |
|     OutOfMemory,
 | |
| };
 | |
| 
 | |
| pub const CborJsonError = error{
 | |
|     BufferUnderrun,
 | |
|     CborIntegerTooLarge,
 | |
|     CborIntegerTooSmall,
 | |
|     CborInvalidType,
 | |
|     CborTooShort,
 | |
|     CborUnsupportedType,
 | |
|     NoSpaceLeft,
 | |
|     OutOfMemory,
 | |
|     Overflow,
 | |
|     SyntaxError,
 | |
|     UnexpectedEndOfInput,
 | |
| };
 | |
| 
 | |
| const cbor_magic_null: u8 = 0xf6;
 | |
| const cbor_magic_true: u8 = 0xf5;
 | |
| const cbor_magic_false: u8 = 0xf4;
 | |
| 
 | |
| const cbor_magic_type_array: u8 = 4;
 | |
| const cbor_magic_type_map: u8 = 5;
 | |
| 
 | |
| const value_type = enum(u8) {
 | |
|     number,
 | |
|     bytes,
 | |
|     string,
 | |
|     array,
 | |
|     map,
 | |
|     tag,
 | |
|     boolean,
 | |
|     null,
 | |
|     any,
 | |
|     more,
 | |
|     unknown,
 | |
| };
 | |
| pub const number = value_type.number;
 | |
| pub const bytes = value_type.bytes;
 | |
| pub const string = value_type.string;
 | |
| pub const array = value_type.array;
 | |
| pub const map = value_type.map;
 | |
| pub const tag = value_type.tag;
 | |
| pub const boolean = value_type.boolean;
 | |
| pub const null_ = value_type.null;
 | |
| pub const any = value_type.any;
 | |
| pub const more = value_type.more;
 | |
| 
 | |
| const null_value_buf = [_]u8{0xF6};
 | |
| pub const null_value: []const u8 = &null_value_buf;
 | |
| 
 | |
| pub fn isNull(val: []const u8) bool {
 | |
|     return eql(u8, val, null_value);
 | |
| }
 | |
| 
 | |
| fn isAny(value: anytype) bool {
 | |
|     return if (comptime @TypeOf(value) == value_type) value == value_type.any else false;
 | |
| }
 | |
| 
 | |
| fn isMore(value: anytype) bool {
 | |
|     return if (comptime @TypeOf(value) == value_type) value == value_type.more else false;
 | |
| }
 | |
| 
 | |
| fn write(writer: anytype, value: u8) @TypeOf(writer).Error!void {
 | |
|     _ = try writer.write(&[_]u8{value});
 | |
| }
 | |
| 
 | |
| fn writeTypedVal(writer: anytype, type_: u8, value: u64) @TypeOf(writer).Error!void {
 | |
|     const t: u8 = type_ << 5;
 | |
|     if (value < 24) {
 | |
|         try write(writer, t | @as(u8, @truncate(value)));
 | |
|     } else if (value < 256) {
 | |
|         try write(writer, t | 24);
 | |
|         try write(writer, @as(u8, @truncate(value)));
 | |
|     } else if (value < 65536) {
 | |
|         try write(writer, t | 25);
 | |
|         try write(writer, @as(u8, @truncate(value >> 8)));
 | |
|         try write(writer, @as(u8, @truncate(value)));
 | |
|     } else if (value < 4294967296) {
 | |
|         try write(writer, t | 26);
 | |
|         try write(writer, @as(u8, @truncate(value >> 24)));
 | |
|         try write(writer, @as(u8, @truncate(value >> 16)));
 | |
|         try write(writer, @as(u8, @truncate(value >> 8)));
 | |
|         try write(writer, @as(u8, @truncate(value)));
 | |
|     } else {
 | |
|         try write(writer, t | 27);
 | |
|         try write(writer, @as(u8, @truncate(value >> 56)));
 | |
|         try write(writer, @as(u8, @truncate(value >> 48)));
 | |
|         try write(writer, @as(u8, @truncate(value >> 40)));
 | |
|         try write(writer, @as(u8, @truncate(value >> 32)));
 | |
|         try write(writer, @as(u8, @truncate(value >> 24)));
 | |
|         try write(writer, @as(u8, @truncate(value >> 16)));
 | |
|         try write(writer, @as(u8, @truncate(value >> 8)));
 | |
|         try write(writer, @as(u8, @truncate(value)));
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn writeArrayHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void {
 | |
|     return writeTypedVal(writer, cbor_magic_type_array, sz);
 | |
| }
 | |
| 
 | |
| pub fn writeMapHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void {
 | |
|     return writeTypedVal(writer, cbor_magic_type_map, sz);
 | |
| }
 | |
| 
 | |
| pub fn writeArray(writer: anytype, args: anytype) @TypeOf(writer).Error!void {
 | |
|     const args_type_info = @typeInfo(@TypeOf(args));
 | |
|     if (args_type_info != .Struct) @compileError("expected tuple or struct argument");
 | |
|     const fields_info = args_type_info.Struct.fields;
 | |
|     try writeArrayHeader(writer, fields_info.len);
 | |
|     inline for (fields_info) |field_info|
 | |
|         try writeValue(writer, @field(args, field_info.name));
 | |
| }
 | |
| 
 | |
| fn writeI64(writer: anytype, value: i64) @TypeOf(writer).Error!void {
 | |
|     return if (value < 0)
 | |
|         writeTypedVal(writer, 1, @as(u64, @bitCast(-(value + 1))))
 | |
|     else
 | |
|         writeTypedVal(writer, 0, @as(u64, @bitCast(value)));
 | |
| }
 | |
| 
 | |
| fn writeU64(writer: anytype, value: u64) @TypeOf(writer).Error!void {
 | |
|     return writeTypedVal(writer, 0, value);
 | |
| }
 | |
| 
 | |
| fn writeString(writer: anytype, s: []const u8) @TypeOf(writer).Error!void {
 | |
|     try writeTypedVal(writer, 3, s.len);
 | |
|     _ = try writer.write(s);
 | |
| }
 | |
| 
 | |
| fn writeBool(writer: anytype, value: bool) @TypeOf(writer).Error!void {
 | |
|     return write(writer, if (value) cbor_magic_true else cbor_magic_false);
 | |
| }
 | |
| 
 | |
| fn writeNull(writer: anytype) @TypeOf(writer).Error!void {
 | |
|     return write(writer, cbor_magic_null);
 | |
| }
 | |
| 
 | |
| fn writeErrorset(writer: anytype, err: anyerror) @TypeOf(writer).Error!void {
 | |
|     var buf: [256]u8 = undefined;
 | |
|     const errmsg = try bufPrint(&buf, "error.{s}", .{@errorName(err)});
 | |
|     return writeString(writer, errmsg);
 | |
| }
 | |
| 
 | |
| pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void {
 | |
|     const T = @TypeOf(value);
 | |
|     switch (@typeInfo(T)) {
 | |
|         .Int, .ComptimeInt => return if (T == u64) writeU64(writer, value) else writeI64(writer, @intCast(value)),
 | |
|         .Bool => return writeBool(writer, value),
 | |
|         .Optional => return if (value) |v| writeValue(writer, v) else writeNull(writer),
 | |
|         .ErrorUnion => return if (value) |v| writeValue(writer, v) else |err| writeValue(writer, err),
 | |
|         .ErrorSet => return writeErrorset(writer, value),
 | |
|         .Union => |info| {
 | |
|             if (info.tag_type) |TagType| {
 | |
|                 comptime var v = void;
 | |
|                 inline for (info.fields) |u_field| {
 | |
|                     if (value == @field(TagType, u_field.name))
 | |
|                         v = @field(value, u_field.name);
 | |
|                 }
 | |
|                 try writeArray(writer, .{
 | |
|                     @typeName(T),
 | |
|                     @tagName(@as(TagType, value)),
 | |
|                     v,
 | |
|                 });
 | |
|             } else {
 | |
|                 try writeArray(writer, .{@typeName(T)});
 | |
|             }
 | |
|         },
 | |
|         .Struct => |info| {
 | |
|             if (info.is_tuple) {
 | |
|                 if (info.fields.len == 0) return writeNull(writer);
 | |
|                 try writeArrayHeader(writer, info.fields.len);
 | |
|                 inline for (info.fields) |f|
 | |
|                     try writeValue(writer, @field(value, f.name));
 | |
|             } else {
 | |
|                 if (info.fields.len == 0) return writeNull(writer);
 | |
|                 try writeMapHeader(writer, info.fields.len);
 | |
|                 inline for (info.fields) |f| {
 | |
|                     try writeString(writer, f.name);
 | |
|                     try writeValue(writer, @field(value, f.name));
 | |
|                 }
 | |
|             }
 | |
|         },
 | |
|         .Pointer => |ptr_info| switch (ptr_info.size) {
 | |
|             .One => return writeValue(writer, value.*),
 | |
|             .Many, .C => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"),
 | |
|             .Slice => {
 | |
|                 if (ptr_info.child == u8) return writeString(writer, value);
 | |
|                 if (value.len == 0) return writeNull(writer);
 | |
|                 try writeArrayHeader(writer, value.len);
 | |
|                 for (value) |elem|
 | |
|                     try writeValue(writer, elem);
 | |
|             },
 | |
|         },
 | |
|         .Array => |info| {
 | |
|             if (info.child == u8) return writeString(writer, &value);
 | |
|             if (value.len == 0) return writeNull(writer);
 | |
|             try writeArrayHeader(writer, value.len);
 | |
|             for (value) |elem|
 | |
|                 try writeValue(writer, elem);
 | |
|         },
 | |
|         .Vector => |info| {
 | |
|             try writeArrayHeader(writer, info.len);
 | |
|             var i: usize = 0;
 | |
|             while (i < info.len) : (i += 1) {
 | |
|                 try writeValue(writer, value[i]);
 | |
|             }
 | |
|         },
 | |
|         .Null => try writeNull(writer),
 | |
|         else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"),
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn fmt(buf: []u8, value: anytype) []const u8 {
 | |
|     var stream = fixedBufferStream(buf);
 | |
|     writeValue(stream.writer(), value) catch unreachable;
 | |
|     return stream.getWritten();
 | |
| }
 | |
| 
 | |
| const CborType = struct { type: u8, minor: u5, major: u3 };
 | |
| 
 | |
| pub fn decodeType(iter: *[]const u8) error{CborTooShort}!CborType {
 | |
|     if (iter.len < 1)
 | |
|         return error.CborTooShort;
 | |
|     const type_: u8 = iter.*[0];
 | |
|     const bits: packed struct { minor: u5, major: u3 } = @bitCast(type_);
 | |
|     iter.* = iter.*[1..];
 | |
|     return .{ .type = type_, .minor = bits.minor, .major = bits.major };
 | |
| }
 | |
| 
 | |
| fn decodeUIntLengthRecurse(iter: *[]const u8, length: usize, acc: u64) !u64 {
 | |
|     if (iter.len < 1)
 | |
|         return error.CborTooShort;
 | |
|     const v: u8 = iter.*[0];
 | |
|     iter.* = iter.*[1..];
 | |
|     var i = acc | v;
 | |
|     if (length == 1)
 | |
|         return i;
 | |
|     i <<= 8;
 | |
|     // return @call(.always_tail, decodeUIntLengthRecurse, .{ iter, length - 1, i });  FIXME: @call(.always_tail) seems broken as of 0.11.0-dev.2964+e9cbdb2cf
 | |
|     return decodeUIntLengthRecurse(iter, length - 1, i);
 | |
| }
 | |
| 
 | |
| fn decodeUIntLength(iter: *[]const u8, length: usize) !u64 {
 | |
|     return decodeUIntLengthRecurse(iter, length, 0);
 | |
| }
 | |
| 
 | |
| fn decodePInt(iter: *[]const u8, minor: u5) !u64 {
 | |
|     if (minor < 24) return minor;
 | |
|     return switch (minor) {
 | |
|         24 => decodeUIntLength(iter, 1), // 1 byte
 | |
|         25 => decodeUIntLength(iter, 2), // 2 byte
 | |
|         26 => decodeUIntLength(iter, 4), // 4 byte
 | |
|         27 => decodeUIntLength(iter, 8), // 8 byte
 | |
|         else => error.CborInvalidType,
 | |
|     };
 | |
| }
 | |
| 
 | |
| fn decodeNInt(iter: *[]const u8, minor: u5) CborError!i64 {
 | |
|     return -@as(i64, @intCast(try decodePInt(iter, minor) + 1));
 | |
| }
 | |
| 
 | |
| pub fn decodeMapHeader(iter: *[]const u8) CborError!usize {
 | |
|     const t = try decodeType(iter);
 | |
|     if (t.type == cbor_magic_null)
 | |
|         return 0;
 | |
|     if (t.major != 5)
 | |
|         return error.CborInvalidType;
 | |
|     return decodePInt(iter, t.minor);
 | |
| }
 | |
| 
 | |
| pub fn decodeArrayHeader(iter: *[]const u8) CborError!usize {
 | |
|     const t = try decodeType(iter);
 | |
|     if (t.type == cbor_magic_null)
 | |
|         return 0;
 | |
|     if (t.major != 4)
 | |
|         return error.CborInvalidType;
 | |
|     return @intCast(try decodePInt(iter, t.minor));
 | |
| }
 | |
| 
 | |
| fn decodeString(iter_: *[]const u8, minor: u5) CborError![]const u8 {
 | |
|     var iter = iter_.*;
 | |
|     const len: usize = @intCast(try decodePInt(&iter, minor));
 | |
|     if (iter.len < len)
 | |
|         return error.CborTooShort;
 | |
|     const s = iter[0..len];
 | |
|     iter = iter[len..];
 | |
|     iter_.* = iter;
 | |
|     return s;
 | |
| }
 | |
| 
 | |
| fn decodeBytes(iter: *[]const u8, minor: u5) CborError![]const u8 {
 | |
|     return decodeString(iter, minor);
 | |
| }
 | |
| 
 | |
| fn decodeJsonArray(iter_: *[]const u8, minor: u5, arr: *json.Array) CborError!bool {
 | |
|     var iter = iter_.*;
 | |
|     var n = try decodePInt(&iter, minor);
 | |
|     while (n > 0) {
 | |
|         const value = try arr.addOne();
 | |
|         if (!try matchJsonValue(&iter, value, arr.allocator))
 | |
|             return false;
 | |
|         n -= 1;
 | |
|     }
 | |
|     iter_.* = iter;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| fn decodeJsonObject(iter_: *[]const u8, minor: u5, obj: *json.ObjectMap) CborError!bool {
 | |
|     var iter = iter_.*;
 | |
|     var n = try decodePInt(&iter, minor);
 | |
|     while (n > 0) {
 | |
|         var key: []u8 = undefined;
 | |
|         var value: json.Value = .null;
 | |
| 
 | |
|         if (!try matchString(&iter, &key))
 | |
|             return false;
 | |
|         if (!try matchJsonValue(&iter, &value, obj.allocator))
 | |
|             return false;
 | |
| 
 | |
|         _ = try obj.getOrPutValue(key, value);
 | |
|         n -= 1;
 | |
|     }
 | |
|     iter_.* = iter;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| pub fn matchInt(comptime T: type, iter_: *[]const u8, val: *T) CborError!bool {
 | |
|     var iter = iter_.*;
 | |
|     const t = try decodeType(&iter);
 | |
|     val.* = switch (t.major) {
 | |
|         0 => blk: { // positive integer
 | |
|             const v = try decodePInt(&iter, t.minor);
 | |
|             if (v > maxInt(T))
 | |
|                 return error.CborIntegerTooLarge;
 | |
|             break :blk @intCast(v);
 | |
|         },
 | |
|         1 => blk: { // negative integer
 | |
|             const v = try decodeNInt(&iter, t.minor);
 | |
|             if (v < minInt(T))
 | |
|                 return error.CborIntegerTooSmall;
 | |
|             break :blk @intCast(v);
 | |
|         },
 | |
| 
 | |
|         else => return false,
 | |
|     };
 | |
|     iter_.* = iter;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| pub fn matchIntValue(comptime T: type, iter: *[]const u8, val: T) CborError!bool {
 | |
|     var v: T = 0;
 | |
|     return if (try matchInt(T, iter, &v)) v == val else false;
 | |
| }
 | |
| 
 | |
| pub fn matchBool(iter_: *[]const u8, v: *bool) CborError!bool {
 | |
|     var iter = iter_.*;
 | |
|     const t = try decodeType(&iter);
 | |
|     if (t.major == 7) { // special
 | |
|         if (t.type == cbor_magic_false) {
 | |
|             v.* = false;
 | |
|             iter_.* = iter;
 | |
|             return true;
 | |
|         }
 | |
|         if (t.type == cbor_magic_true) {
 | |
|             v.* = true;
 | |
|             iter_.* = iter;
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| fn matchBoolValue(iter: *[]const u8, val: bool) CborError!bool {
 | |
|     var v: bool = false;
 | |
|     return if (try matchBool(iter, &v)) v == val else false;
 | |
| }
 | |
| 
 | |
| fn skipString(iter: *[]const u8, minor: u5) CborError!void {
 | |
|     const len: usize = @intCast(try decodePInt(iter, minor));
 | |
|     if (iter.len < len)
 | |
|         return error.CborTooShort;
 | |
|     iter.* = iter.*[len..];
 | |
| }
 | |
| 
 | |
| fn skipBytes(iter: *[]const u8, minor: u5) CborError!void {
 | |
|     return skipString(iter, minor);
 | |
| }
 | |
| 
 | |
| fn skipArray(iter: *[]const u8, minor: u5) CborError!void {
 | |
|     var len = try decodePInt(iter, minor);
 | |
|     while (len > 0) {
 | |
|         try skipValue(iter);
 | |
|         len -= 1;
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn skipMap(iter: *[]const u8, minor: u5) CborError!void {
 | |
|     var len = try decodePInt(iter, minor);
 | |
|     len *= 2;
 | |
|     while (len > 0) {
 | |
|         try skipValue(iter);
 | |
|         len -= 1;
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn skipValue(iter: *[]const u8) CborError!void {
 | |
|     const t = try decodeType(iter);
 | |
|     try skipValueType(iter, t.major, t.minor);
 | |
| }
 | |
| 
 | |
| fn skipValueType(iter: *[]const u8, major: u3, minor: u5) CborError!void {
 | |
|     switch (major) {
 | |
|         0 => { // positive integer
 | |
|             _ = try decodePInt(iter, minor);
 | |
|         },
 | |
|         1 => { // negative integer
 | |
|             _ = try decodeNInt(iter, minor);
 | |
|         },
 | |
|         2 => { // bytes
 | |
|             try skipBytes(iter, minor);
 | |
|         },
 | |
|         3 => { // string
 | |
|             try skipString(iter, minor);
 | |
|         },
 | |
|         4 => { // array
 | |
|             try skipArray(iter, minor);
 | |
|         },
 | |
|         5 => { // map
 | |
|             try skipMap(iter, minor);
 | |
|         },
 | |
|         6 => { // tag
 | |
|             return error.CborInvalidType;
 | |
|         },
 | |
|         7 => { // special
 | |
|             return;
 | |
|         },
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn matchType(iter_: *[]const u8, v: *value_type) CborError!bool {
 | |
|     var iter = iter_.*;
 | |
|     const t = try decodeType(&iter);
 | |
|     try skipValueType(&iter, t.major, t.minor);
 | |
|     switch (t.major) {
 | |
|         0, 1 => v.* = value_type.number, // positive integer or negative integer
 | |
|         2 => v.* = value_type.bytes, // bytes
 | |
|         3 => v.* = value_type.string, // string
 | |
|         4 => v.* = value_type.array, // array
 | |
|         5 => v.* = value_type.map, // map
 | |
|         7 => { // special
 | |
|             if (t.type == cbor_magic_null) {
 | |
|                 v.* = value_type.null;
 | |
|             } else {
 | |
|                 if (t.type == cbor_magic_false or t.type == cbor_magic_true) {
 | |
|                     v.* = value_type.boolean;
 | |
|                 } else {
 | |
|                     return false;
 | |
|                 }
 | |
|             }
 | |
|         },
 | |
|         else => return false,
 | |
|     }
 | |
|     iter_.* = iter;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| fn matchValueType(iter: *[]const u8, t: value_type) CborError!bool {
 | |
|     var v: value_type = value_type.unknown;
 | |
|     return if (try matchType(iter, &v)) (t == value_type.any or t == v) else false;
 | |
| }
 | |
| 
 | |
| pub fn matchString(iter_: *[]const u8, val: *[]const u8) CborError!bool {
 | |
|     var iter = iter_.*;
 | |
|     const t = try decodeType(&iter);
 | |
|     val.* = switch (t.major) {
 | |
|         2 => try decodeBytes(&iter, t.minor), // bytes
 | |
|         3 => try decodeString(&iter, t.minor), // string
 | |
|         else => return false,
 | |
|     };
 | |
|     iter_.* = iter;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| fn matchStringValue(iter: *[]const u8, lit: []const u8) CborError!bool {
 | |
|     var val: []const u8 = undefined;
 | |
|     return if (try matchString(iter, &val)) eql(u8, val, lit) else false;
 | |
| }
 | |
| 
 | |
| fn matchError(comptime T: type) noreturn {
 | |
|     @compileError("cannot match type '" ++ @typeName(T) ++ "' to cbor stream");
 | |
| }
 | |
| 
 | |
| pub fn matchValue(iter: *[]const u8, value: anytype) CborError!bool {
 | |
|     if (@TypeOf(value) == value_type)
 | |
|         return matchValueType(iter, value);
 | |
|     const T = comptime @TypeOf(value);
 | |
|     if (comptime isExtractor(T))
 | |
|         return value.extract(iter);
 | |
|     return switch (comptime @typeInfo(T)) {
 | |
|         .Int => return matchIntValue(T, iter, value),
 | |
|         .ComptimeInt => return matchIntValue(i64, iter, value),
 | |
|         .Bool => matchBoolValue(iter, value),
 | |
|         .Pointer => |info| switch (info.size) {
 | |
|             .One => matchValue(iter, value.*),
 | |
|             .Many, .C => matchError(T),
 | |
|             .Slice => if (info.child == u8) matchStringValue(iter, value) else matchArray(iter, value, info),
 | |
|         },
 | |
|         .Struct => |info| if (info.is_tuple)
 | |
|             matchArray(iter, value, info)
 | |
|         else
 | |
|             matchError(T),
 | |
|         .Array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info),
 | |
|         else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"),
 | |
|     };
 | |
| }
 | |
| 
 | |
| fn matchJsonValue(iter_: *[]const u8, v: *json.Value, a: std.mem.Allocator) CborError!bool {
 | |
|     var iter = iter_.*;
 | |
|     const t = try decodeType(&iter);
 | |
|     const ret = switch (t.major) {
 | |
|         0 => ret: { // positive integer
 | |
|             v.* = json.Value{ .integer = @intCast(try decodePInt(&iter, t.minor)) };
 | |
|             break :ret true;
 | |
|         },
 | |
|         1 => ret: { // negative integer
 | |
|             v.* = json.Value{ .integer = try decodeNInt(&iter, t.minor) };
 | |
|             break :ret true;
 | |
|         },
 | |
|         2 => ret: { // bytes
 | |
|             break :ret false;
 | |
|         },
 | |
|         3 => ret: { // string
 | |
|             v.* = json.Value{ .string = try decodeString(&iter, t.minor) };
 | |
|             break :ret true;
 | |
|         },
 | |
|         4 => ret: { // array
 | |
|             v.* = json.Value{ .array = json.Array.init(a) };
 | |
|             break :ret try decodeJsonArray(&iter, t.minor, &v.array);
 | |
|         },
 | |
|         5 => ret: { // map
 | |
|             v.* = json.Value{ .object = json.ObjectMap.init(a) };
 | |
|             break :ret try decodeJsonObject(&iter, t.minor, &v.object);
 | |
|         },
 | |
|         6 => ret: { // tag
 | |
|             break :ret false;
 | |
|         },
 | |
|         7 => ret: { // special
 | |
|             switch (t.type) {
 | |
|                 cbor_magic_false => {
 | |
|                     v.* = json.Value{ .bool = false };
 | |
|                     break :ret true;
 | |
|                 },
 | |
|                 cbor_magic_true => {
 | |
|                     v.* = json.Value{ .bool = true };
 | |
|                     break :ret true;
 | |
|                 },
 | |
|                 cbor_magic_null => {
 | |
|                     v.* = json.Value{ .null = {} };
 | |
|                     break :ret true;
 | |
|                 },
 | |
|                 else => break :ret false,
 | |
|             }
 | |
|         },
 | |
|     };
 | |
|     if (ret) iter_.* = iter;
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| fn matchArrayMore(iter_: *[]const u8, n_: u64) CborError!bool {
 | |
|     var iter = iter_.*;
 | |
|     var n = n_;
 | |
|     while (n > 0) {
 | |
|         if (!try matchValue(&iter, value_type.any))
 | |
|             return false;
 | |
|         n -= 1;
 | |
|     }
 | |
|     iter_.* = iter;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| fn matchArray(iter_: *[]const u8, arr: anytype, info: anytype) CborError!bool {
 | |
|     var iter = iter_.*;
 | |
|     var n = try decodeArrayHeader(&iter);
 | |
|     inline for (info.fields) |f| {
 | |
|         const value = @field(arr, f.name);
 | |
|         if (isMore(value))
 | |
|             break;
 | |
|     } else if (info.fields.len != n)
 | |
|         return false;
 | |
|     inline for (info.fields) |f| {
 | |
|         const value = @field(arr, f.name);
 | |
|         if (isMore(value))
 | |
|             if (try matchArrayMore(&iter, n)) {
 | |
|                 iter_.* = iter;
 | |
|                 return true;
 | |
|             } else {
 | |
|                 return false;
 | |
|             };
 | |
|         if (n == 0) return false;
 | |
|         const matched = try matchValue(&iter, @field(arr, f.name));
 | |
|         if (!matched) return false;
 | |
|         n -= 1;
 | |
|     }
 | |
|     if (n == 0) iter_.* = iter;
 | |
|     return n == 0;
 | |
| }
 | |
| 
 | |
| fn matchJsonObject(iter_: *[]const u8, obj: *json.ObjectMap) !bool {
 | |
|     var iter = iter_.*;
 | |
|     const t = try decodeType(&iter);
 | |
|     if (t.type == cbor_magic_null)
 | |
|         return true;
 | |
|     if (t.major != 5)
 | |
|         return error.CborInvalidType;
 | |
|     const ret = try decodeJsonObject(&iter, t.minor, obj);
 | |
|     if (ret) iter_.* = iter;
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| pub fn match(buf: []const u8, pattern: anytype) CborError!bool {
 | |
|     var iter: []const u8 = buf;
 | |
|     return matchValue(&iter, pattern);
 | |
| }
 | |
| 
 | |
| fn extractError(comptime T: type) noreturn {
 | |
|     @compileError("cannot extract type '" ++ @typeName(T) ++ "' from cbor stream");
 | |
| }
 | |
| 
 | |
| fn hasExtractorTag(info: anytype) bool {
 | |
|     if (info.is_tuple) return false;
 | |
|     inline for (info.decls) |decl| {
 | |
|         if (comptime eql(u8, decl.name, "EXTRACTOR_TAG"))
 | |
|             return true;
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| fn isExtractor(comptime T: type) bool {
 | |
|     return comptime switch (@typeInfo(T)) {
 | |
|         .Struct => |info| hasExtractorTag(info),
 | |
|         else => false,
 | |
|     };
 | |
| }
 | |
| 
 | |
| const JsonValueExtractor = struct {
 | |
|     dest: *T,
 | |
|     const Self = @This();
 | |
|     pub const EXTRACTOR_TAG = struct {};
 | |
|     const T = json.Value;
 | |
| 
 | |
|     pub fn init(dest: *T) Self {
 | |
|         return .{ .dest = dest };
 | |
|     }
 | |
| 
 | |
|     pub fn extract(self: Self, iter: *[]const u8) CborError!bool {
 | |
|         var null_heap_: [0]u8 = undefined;
 | |
|         var heap = fba.init(&null_heap_);
 | |
|         return matchJsonValue(iter, self.dest, heap.allocator());
 | |
|     }
 | |
| };
 | |
| 
 | |
| const JsonObjectExtractor = struct {
 | |
|     dest: *T,
 | |
|     const Self = @This();
 | |
|     pub const EXTRACTOR_TAG = struct {};
 | |
|     const T = json.ObjectMap;
 | |
| 
 | |
|     pub fn init(dest: *T) Self {
 | |
|         return .{ .dest = dest };
 | |
|     }
 | |
| 
 | |
|     pub fn extract(self: Self, iter: *[]const u8) CborError!bool {
 | |
|         return matchJsonObject(iter, self.dest);
 | |
|     }
 | |
| };
 | |
| 
 | |
| fn Extractor(comptime T: type) type {
 | |
|     if (T == json.Value)
 | |
|         return JsonValueExtractor;
 | |
|     if (T == json.ObjectMap)
 | |
|         return JsonObjectExtractor;
 | |
|     return struct {
 | |
|         dest: *T,
 | |
|         const Self = @This();
 | |
|         pub const EXTRACTOR_TAG = struct {};
 | |
| 
 | |
|         pub fn init(dest: *T) Self {
 | |
|             return .{ .dest = dest };
 | |
|         }
 | |
| 
 | |
|         pub fn extract(self: Self, iter: *[]const u8) CborError!bool {
 | |
|             switch (comptime @typeInfo(T)) {
 | |
|                 .Int, .ComptimeInt => return matchInt(T, iter, self.dest),
 | |
|                 .Bool => return matchBool(iter, self.dest),
 | |
|                 .Pointer => |ptr_info| switch (ptr_info.size) {
 | |
|                     .Slice => {
 | |
|                         if (ptr_info.child == u8) return matchString(iter, self.dest) else extractError(T);
 | |
|                     },
 | |
|                     else => extractError(T),
 | |
|                 },
 | |
|                 else => extractError(T),
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| }
 | |
| 
 | |
| fn ExtractorType(comptime T: type) type {
 | |
|     const T_type_info = @typeInfo(T);
 | |
|     if (T_type_info != .Pointer) @compileError("extract requires a pointer argument");
 | |
|     return Extractor(T_type_info.Pointer.child);
 | |
| }
 | |
| 
 | |
| pub fn extract(dest: anytype) ExtractorType(@TypeOf(dest)) {
 | |
|     comptime {
 | |
|         if (!isExtractor(ExtractorType(@TypeOf(dest))))
 | |
|             @compileError("isExtractor self check failed for " ++ @typeName(ExtractorType(@TypeOf(dest))));
 | |
|     }
 | |
|     return ExtractorType(@TypeOf(dest)).init(dest);
 | |
| }
 | |
| 
 | |
| const CborExtractor = struct {
 | |
|     dest: *[]const u8,
 | |
|     const Self = @This();
 | |
|     pub const EXTRACTOR_TAG = struct {};
 | |
| 
 | |
|     pub fn init(dest: *[]const u8) Self {
 | |
|         return .{ .dest = dest };
 | |
|     }
 | |
| 
 | |
|     pub fn extract(self: Self, iter: *[]const u8) CborError!bool {
 | |
|         const b = iter.*;
 | |
|         try skipValue(iter);
 | |
|         self.dest.* = b[0..(b.len - iter.len)];
 | |
|         return true;
 | |
|     }
 | |
| };
 | |
| 
 | |
| pub fn extract_cbor(dest: *[]const u8) CborExtractor {
 | |
|     return CborExtractor.init(dest);
 | |
| }
 | |
| 
 | |
| pub fn JsonStream(comptime T: type) type {
 | |
|     return struct {
 | |
|         const Writer = T.Writer;
 | |
|         const JsonWriter = json.WriteStream(Writer, .{ .checked_to_fixed_depth = 256 });
 | |
| 
 | |
|         fn jsonWriteArray(w: *JsonWriter, iter: *[]const u8, minor: u5) !void {
 | |
|             var count = try decodePInt(iter, minor);
 | |
|             try w.beginArray();
 | |
|             while (count > 0) : (count -= 1) {
 | |
|                 try jsonWriteValue(w, iter);
 | |
|             }
 | |
|             try w.endArray();
 | |
|         }
 | |
| 
 | |
|         fn jsonWriteMap(w: *JsonWriter, iter: *[]const u8, minor: u5) !void {
 | |
|             var count = try decodePInt(iter, minor);
 | |
|             try w.beginObject();
 | |
|             while (count > 0) : (count -= 1) {
 | |
|                 const t = try decodeType(iter);
 | |
|                 if (t.major != 3) return error.CborInvalidType;
 | |
|                 try w.objectField(try decodeString(iter, t.minor));
 | |
|                 try jsonWriteValue(w, iter);
 | |
|             }
 | |
|             try w.endObject();
 | |
|         }
 | |
| 
 | |
|         pub fn jsonWriteValue(w: *JsonWriter, iter: *[]const u8) (CborJsonError || Writer.Error)!void {
 | |
|             const t = try decodeType(iter);
 | |
|             if (t.type == cbor_magic_false)
 | |
|                 return w.write(false);
 | |
|             if (t.type == cbor_magic_true)
 | |
|                 return w.write(true);
 | |
|             if (t.type == cbor_magic_null)
 | |
|                 return w.write(null);
 | |
|             return switch (t.major) {
 | |
|                 0 => w.write(try decodePInt(iter, t.minor)), // positive integer
 | |
|                 1 => w.write(try decodeNInt(iter, t.minor)), // negative integer
 | |
|                 2 => error.CborUnsupportedType, // bytes
 | |
|                 3 => w.write(try decodeString(iter, t.minor)), // string
 | |
|                 4 => jsonWriteArray(w, iter, t.minor), // array
 | |
|                 5 => jsonWriteMap(w, iter, t.minor), // map
 | |
|                 else => error.CborInvalidType,
 | |
|             };
 | |
|         }
 | |
|     };
 | |
| }
 | |
| 
 | |
| pub fn toJson(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]const u8 {
 | |
|     var fbs = fixedBufferStream(json_buf);
 | |
|     var s = json.writeStream(fbs.writer(), .{});
 | |
|     var iter: []const u8 = cbor_buf;
 | |
|     try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter);
 | |
|     return fbs.getWritten();
 | |
| }
 | |
| 
 | |
| pub fn toJsonAlloc(a: std.mem.Allocator, cbor_buf: []const u8) CborJsonError![]const u8 {
 | |
|     var buf = std.ArrayList(u8).init(a);
 | |
|     defer buf.deinit();
 | |
|     var s = json.writeStream(buf.writer(), .{});
 | |
|     var iter: []const u8 = cbor_buf;
 | |
|     try JsonStream(@TypeOf(buf)).jsonWriteValue(&s, &iter);
 | |
|     return buf.toOwnedSlice();
 | |
| }
 | |
| 
 | |
| pub fn toJsonPretty(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]const u8 {
 | |
|     var fbs = fixedBufferStream(json_buf);
 | |
|     var s = json.writeStream(fbs.writer(), .{ .whitespace = .indent_1 });
 | |
|     var iter: []const u8 = cbor_buf;
 | |
|     try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter);
 | |
|     return fbs.getWritten();
 | |
| }
 | |
| 
 | |
| pub fn toJsonPrettyAlloc(a: std.mem.Allocator, cbor_buf: []const u8) CborJsonError![]const u8 {
 | |
|     var buf = std.ArrayList(u8).init(a);
 | |
|     defer buf.deinit();
 | |
|     var s = json.writeStream(buf.writer(), .{ .whitespace = .indent_1 });
 | |
|     var iter: []const u8 = cbor_buf;
 | |
|     try JsonStream(@TypeOf(buf)).jsonWriteValue(&s, &iter);
 | |
|     return buf.toOwnedSlice();
 | |
| }
 | |
| 
 | |
| fn writeJsonValue(writer: anytype, value: json.Value) !void {
 | |
|     try switch (value) {
 | |
|         .array => |_| unreachable,
 | |
|         .object => |_| unreachable,
 | |
|         .null => writeNull(writer),
 | |
|         .float => |_| error.CborUnsupportedType,
 | |
|         inline else => |v| writeValue(writer, v),
 | |
|     };
 | |
| }
 | |
| 
 | |
| fn jsonScanUntil(writer: anytype, scanner: *json.Scanner, end_token: anytype) CborJsonError!usize {
 | |
|     var partial = try std.BoundedArray(u8, 4096).init(0);
 | |
|     var count: usize = 0;
 | |
| 
 | |
|     var token = try scanner.next();
 | |
|     while (token != end_token) : (token = try scanner.next()) {
 | |
|         count += 1;
 | |
|         switch (token) {
 | |
|             .object_begin => try writeJsonObject(writer, scanner),
 | |
|             .array_begin => try writeJsonArray(writer, scanner),
 | |
| 
 | |
|             .true => try writeBool(writer, true),
 | |
|             .false => try writeBool(writer, false),
 | |
|             .null => try writeNull(writer),
 | |
| 
 | |
|             .number => |v| {
 | |
|                 try partial.appendSlice(v);
 | |
|                 try writeJsonValue(writer, json.Value.parseFromNumberSlice(partial.slice()));
 | |
|                 try partial.resize(0);
 | |
|             },
 | |
|             .partial_number => |v| {
 | |
|                 try partial.appendSlice(v);
 | |
|                 count -= 1;
 | |
|             },
 | |
| 
 | |
|             .string => |v| {
 | |
|                 try partial.appendSlice(v);
 | |
|                 try writeString(writer, partial.slice());
 | |
|                 try partial.resize(0);
 | |
|             },
 | |
|             .partial_string => |v| {
 | |
|                 try partial.appendSlice(v);
 | |
|                 count -= 1;
 | |
|             },
 | |
|             .partial_string_escaped_1 => |v| {
 | |
|                 try partial.appendSlice(&v);
 | |
|                 count -= 1;
 | |
|             },
 | |
|             .partial_string_escaped_2 => |v| {
 | |
|                 try partial.appendSlice(&v);
 | |
|                 count -= 1;
 | |
|             },
 | |
|             .partial_string_escaped_3 => |v| {
 | |
|                 try partial.appendSlice(&v);
 | |
|                 count -= 1;
 | |
|             },
 | |
|             .partial_string_escaped_4 => |v| {
 | |
|                 try partial.appendSlice(&v);
 | |
|                 count -= 1;
 | |
|             },
 | |
| 
 | |
|             else => return error.SyntaxError,
 | |
|         }
 | |
|     }
 | |
|     return count;
 | |
| }
 | |
| 
 | |
| pub const local_heap_size = 4096 * 16;
 | |
| 
 | |
| fn writeJsonArray(writer_: anytype, scanner: *json.Scanner) CborJsonError!void {
 | |
|     var buf: [local_heap_size]u8 = undefined;
 | |
|     var stream = fixedBufferStream(&buf);
 | |
|     const writer = stream.writer();
 | |
|     const count = try jsonScanUntil(writer, scanner, .array_end);
 | |
|     try writeArrayHeader(writer_, count);
 | |
|     try writer_.writeAll(stream.getWritten());
 | |
| }
 | |
| 
 | |
| fn writeJsonObject(writer_: anytype, scanner: *json.Scanner) CborJsonError!void {
 | |
|     var buf: [local_heap_size]u8 = undefined;
 | |
|     var stream = fixedBufferStream(&buf);
 | |
|     const writer = stream.writer();
 | |
|     const count = try jsonScanUntil(writer, scanner, .object_end);
 | |
|     try writeMapHeader(writer_, count / 2);
 | |
|     try writer_.writeAll(stream.getWritten());
 | |
| }
 | |
| 
 | |
| pub fn fromJson(json_buf: []const u8, cbor_buf: []u8) ![]const u8 {
 | |
|     var local_heap_: [local_heap_size]u8 = undefined;
 | |
|     var heap = fba.init(&local_heap_);
 | |
|     var stream = fixedBufferStream(cbor_buf);
 | |
|     const writer = stream.writer();
 | |
| 
 | |
|     var scanner = json.Scanner.initCompleteInput(heap.allocator(), json_buf);
 | |
|     defer scanner.deinit();
 | |
| 
 | |
|     _ = try jsonScanUntil(writer, &scanner, .end_of_document);
 | |
|     return stream.getWritten();
 | |
| }
 | |
| 
 | |
| pub fn fromJsonAlloc(a: std.mem.Allocator, json_buf: []const u8) ![]const u8 {
 | |
|     var stream = std.ArrayList(u8).init(a);
 | |
|     defer stream.deinit();
 | |
|     const writer = stream.writer();
 | |
| 
 | |
|     var scanner = json.Scanner.initCompleteInput(a, json_buf);
 | |
|     defer scanner.deinit();
 | |
| 
 | |
|     _ = try jsonScanUntil(writer, &scanner, .end_of_document);
 | |
|     return stream.toOwnedSlice();
 | |
| }
 |