219 lines
6.6 KiB
Zig
219 lines
6.6 KiB
Zig
const std = @import("std");
|
|
|
|
const Object = @import("root.zig").Object;
|
|
|
|
const MapEntry = @import("root.zig").MapEntry;
|
|
|
|
const SerialiseError = error{
|
|
OutOfMemory,
|
|
StringTooLong,
|
|
BinaryTooLong,
|
|
ArrayTooLong,
|
|
MapTooLong,
|
|
ExtTooLong,
|
|
};
|
|
|
|
fn intInRange(comptime T: type, i: i64) bool {
|
|
return std.math.minInt(T) <= i and i <= std.math.maxInt(T);
|
|
}
|
|
|
|
fn serialise_int(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), i: i64) SerialiseError!void {
|
|
if (i < 0 and i >= std.math.minInt(i6)) { // neg fixint
|
|
std.mem.writeInt(i8, try buf.addManyAsArray(alloc, 1), @intCast(i), .big);
|
|
|
|
return;
|
|
}
|
|
|
|
inline for (0.., &[_]type{ i8, i16, i32, i64 }) |n, T| {
|
|
if (intInRange(T, i)) {
|
|
try buf.ensureUnusedCapacity(alloc, 1 + @sizeOf(T));
|
|
|
|
try buf.append(alloc, 0xd0 + n);
|
|
|
|
std.mem.writeInt(T, try buf.addManyAsArray(alloc, @sizeOf(T)), @intCast(i), .big);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
unreachable;
|
|
}
|
|
|
|
fn uintInRange(comptime T: type, u: u64) bool {
|
|
return u <= std.math.maxInt(T);
|
|
}
|
|
|
|
fn serialise_uint(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), u: u64) SerialiseError!void {
|
|
if (uintInRange(u7, u)) { // pos fixint
|
|
std.mem.writeInt(u8, try buf.addManyAsArray(alloc, 1), @intCast(u), .big);
|
|
|
|
return;
|
|
}
|
|
|
|
inline for (0.., &[_]type{ u8, u16, u32, u64 }) |n, T| {
|
|
if (uintInRange(T, u)) {
|
|
try buf.ensureUnusedCapacity(alloc, 1 + @sizeOf(T));
|
|
|
|
try buf.append(alloc, 0xcc + n);
|
|
|
|
std.mem.writeInt(T, try buf.addManyAsArray(alloc, @sizeOf(T)), @intCast(u), .big);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
unreachable;
|
|
}
|
|
|
|
fn serialise_raw(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), s: []const u8, comptime kind: enum { string, binary }) SerialiseError!void {
|
|
// TODO: should we validate that `s` is UTF-8 here?
|
|
|
|
if (kind == .string and s.len <= 31) { // fixstr
|
|
try buf.ensureUnusedCapacity(alloc, 1 + s.len);
|
|
|
|
try buf.append(alloc, 0b101_00000 | @as(u8, @intCast(s.len)));
|
|
|
|
try buf.appendSlice(alloc, s);
|
|
|
|
return;
|
|
}
|
|
|
|
inline for (0.., &[_]type{ u8, u16, u32 }) |i, T| {
|
|
if (s.len <= std.math.maxInt(T)) {
|
|
try buf.ensureUnusedCapacity(alloc, 1 + @sizeOf(T) + s.len);
|
|
|
|
switch (kind) {
|
|
.string => try buf.append(alloc, 0xd9 + i),
|
|
.binary => try buf.append(alloc, 0xc4 + i),
|
|
}
|
|
|
|
std.mem.writeInt(T, try buf.addManyAsArray(alloc, @sizeOf(T)), @intCast(s.len), .big);
|
|
|
|
try buf.appendSlice(alloc, s);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
return switch (kind) {
|
|
.string => error.StringTooLong,
|
|
.binary => error.BinaryTooLong,
|
|
};
|
|
}
|
|
|
|
fn serialise_array(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), array: []const Object) SerialiseError!void {
|
|
if (array.len <= 15) {
|
|
try buf.append(alloc, 0b1001_0000 | @as(u8, @intCast(array.len)));
|
|
} else if (array.len <= std.math.maxInt(u16)) {
|
|
try buf.append(alloc, 0xdc);
|
|
|
|
std.mem.writeInt(u16, try buf.addManyAsArray(alloc, @sizeOf(u16)), @intCast(array.len), .big);
|
|
} else if (array.len <= std.math.maxInt(u32)) {
|
|
try buf.append(alloc, 0xdd);
|
|
|
|
std.mem.writeInt(u32, try buf.addManyAsArray(alloc, @sizeOf(u32)), @intCast(array.len), .big);
|
|
} else {
|
|
return error.ArrayTooLong;
|
|
}
|
|
|
|
for (array) |obj| {
|
|
try serialise_into_buf(alloc, buf, obj);
|
|
}
|
|
}
|
|
|
|
fn serialise_map(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), map: []const MapEntry) SerialiseError!void {
|
|
if (map.len <= 15) {
|
|
try buf.append(alloc, 0b1000_0000 | @as(u8, @intCast(map.len)));
|
|
} else if (map.len <= std.math.maxInt(u16)) {
|
|
try buf.append(alloc, 0xde);
|
|
|
|
std.mem.writeInt(u16, try buf.addManyAsArray(alloc, @sizeOf(u16)), @intCast(map.len), .big);
|
|
} else if (map.len <= std.math.maxInt(u32)) {
|
|
try buf.append(alloc, 0xdf);
|
|
|
|
std.mem.writeInt(u32, try buf.addManyAsArray(alloc, @sizeOf(u32)), @intCast(map.len), .big);
|
|
} else {
|
|
return error.MapTooLong;
|
|
}
|
|
|
|
for (map) |entry| {
|
|
try serialise_into_buf(alloc, buf, entry.key);
|
|
try serialise_into_buf(alloc, buf, entry.value);
|
|
}
|
|
}
|
|
|
|
fn serialise_ext(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), type_: u8, bytes: []u8) SerialiseError!void {
|
|
// fixext
|
|
for (0.., [_]usize{ 1, 2, 4, 8, 16 }) |n, len| {
|
|
if (bytes.len == len) {
|
|
try buf.ensureUnusedCapacity(alloc, 1 + 1 + len);
|
|
|
|
try buf.append(alloc, 0xd4 + @as(u8, @intCast(n)));
|
|
try buf.append(alloc, type_);
|
|
|
|
try buf.appendSlice(alloc, bytes);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ext
|
|
inline for (0.., [_]type{ u8, u16, u32 }) |n, T| {
|
|
if (bytes.len <= std.math.maxInt(T)) {
|
|
try buf.ensureUnusedCapacity(alloc, 1 + @sizeOf(T) + bytes.len);
|
|
|
|
try buf.append(alloc, 0xc7 + n);
|
|
|
|
std.mem.writeInt(T, try buf.addManyAsArray(alloc, @sizeOf(T)), @intCast(bytes.len), .big);
|
|
|
|
try buf.append(alloc, type_);
|
|
|
|
try buf.appendSlice(alloc, bytes);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
return error.ExtTooLong;
|
|
}
|
|
|
|
fn serialise_into_buf(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), obj: Object) SerialiseError!void {
|
|
switch (obj) {
|
|
.nil => try buf.append(alloc, 0xc0),
|
|
.bool => |b| if (b) {
|
|
try buf.append(alloc, 0xc3);
|
|
} else {
|
|
try buf.append(alloc, 0xc2);
|
|
},
|
|
.integer => |i| try serialise_int(alloc, buf, i),
|
|
.uinteger => |u| try serialise_uint(alloc, buf, u),
|
|
.float32 => |f| {
|
|
try buf.ensureUnusedCapacity(alloc, 1 + 4);
|
|
|
|
try buf.append(alloc, 0xca);
|
|
|
|
std.mem.writeInt(u32, try buf.addManyAsArray(alloc, 4), @bitCast(f), .big);
|
|
},
|
|
.float64 => |f| {
|
|
try buf.ensureUnusedCapacity(alloc, 1 + 8);
|
|
|
|
try buf.append(alloc, 0xcb);
|
|
|
|
std.mem.writeInt(u64, try buf.addManyAsArray(alloc, 8), @bitCast(f), .big);
|
|
},
|
|
.string => |s| try serialise_raw(alloc, buf, s, .string),
|
|
.binary => |b| try serialise_raw(alloc, buf, b, .binary),
|
|
.array => |array| try serialise_array(alloc, buf, array),
|
|
.map => |map| try serialise_map(alloc, buf, map),
|
|
.extension => |ext| try serialise_ext(alloc, buf, ext.type, ext.bytes),
|
|
}
|
|
}
|
|
|
|
pub fn serialise(alloc: std.mem.Allocator, obj: Object) SerialiseError![]u8 {
|
|
var buf = std.ArrayListUnmanaged(u8){};
|
|
defer buf.deinit(alloc);
|
|
|
|
try serialise_into_buf(alloc, &buf, obj);
|
|
|
|
return try buf.toOwnedSlice(alloc);
|
|
}
|