msgpack-zig/src/serialise.zig

219 lines
5 KiB
Zig
Raw Normal View History

2025-04-02 02:44:26 +02:00
const std = @import("std");
const Object = @import("root.zig").Object;
const Tag = @import("root.zig").Tag;
const SerialiseError = error{
OutOfMemory,
StringTooLong,
BinaryTooLong,
};
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, i: i64) SerialiseError![]u8 {
if (i < 0 and i >= std.math.minInt(i6)) {
// neg fixint
const bytes = try alloc.alloc(u8, 1);
std.mem.writeInt(i8, bytes[0..1], @intCast(i), .big);
return bytes;
}
if (intInRange(i8, i)) { // int8
const bytes = try alloc.alloc(u8, 1 + 1);
bytes[0] = 0xd0;
std.mem.writeInt(i8, bytes[1..2], @intCast(i), .big);
return bytes;
}
if (intInRange(i16, i)) { // int16
const bytes = try alloc.alloc(u8, 1 + 2);
bytes[0] = 0xd1;
std.mem.writeInt(i16, bytes[1..3], @intCast(i), .big);
return bytes;
}
if (intInRange(i32, i)) { // int32
const bytes = try alloc.alloc(u8, 1 + 4);
bytes[0] = 0xd2;
std.mem.writeInt(i32, bytes[1..5], @intCast(i), .big);
return bytes;
}
// int64
const bytes = try alloc.alloc(u8, 1 + 8);
bytes[0] = 0xd3;
std.mem.writeInt(i64, bytes[1..9], i, .big);
return bytes;
}
fn uintInRange(comptime T: type, u: u64) bool {
return u <= std.math.maxInt(T);
}
fn serialise_uint(alloc: std.mem.Allocator, u: u64) SerialiseError![]u8 {
if (uintInRange(u7, u)) { // pos fixint
const bytes = try alloc.alloc(u8, 1);
std.mem.writeInt(u8, bytes[0..1], @intCast(u), .big);
return bytes;
}
if (uintInRange(u8, u)) { // uint8
const bytes = try alloc.alloc(u8, 1 + 1);
bytes[0] = 0xcc;
std.mem.writeInt(u8, bytes[1..2], @intCast(u), .big);
return bytes;
}
if (uintInRange(u16, u)) { // uint16
const bytes = try alloc.alloc(u8, 1 + 2);
bytes[0] = 0xcd;
std.mem.writeInt(u16, bytes[1..3], @intCast(u), .big);
return bytes;
}
if (uintInRange(u32, u)) { // uint32
const bytes = try alloc.alloc(u8, 1 + 4);
bytes[0] = 0xce;
std.mem.writeInt(u32, bytes[1..5], @intCast(u), .big);
return bytes;
}
// uint64
const bytes = try alloc.alloc(u8, 1 + 8);
bytes[0] = 0xcf;
std.mem.writeInt(u64, bytes[1..9], u, .big);
return bytes;
}
fn serialise_raw(alloc: std.mem.Allocator, s: []const u8, comptime kind: enum { string, binary }) SerialiseError![]u8 {
// TODO: should we validate that s in UTF-8 here?
if (kind == .string and s.len <= 31) { // fixstr
const bytes = try alloc.alloc(u8, 1 + s.len);
bytes[0] = 0b101_00000 | @as(u8, @intCast(s.len));
@memcpy(bytes[1..], s);
return bytes;
}
if (s.len <= std.math.maxInt(u8)) { // str8 / bin8
const bytes = try alloc.alloc(u8, 1 + 1 + s.len);
bytes[0] = switch (kind) {
.string => 0xd9,
.binary => 0xc4,
};
bytes[1] = @as(u8, @intCast(s.len));
@memcpy(bytes[2..], s);
return bytes;
}
if (s.len <= std.math.maxInt(u16)) { // str16 / bin16
const bytes = try alloc.alloc(u8, 1 + 2 + s.len);
bytes[0] = switch (kind) {
.string => 0xda,
.binary => 0xc5,
};
std.mem.writeInt(u16, bytes[1..3], @intCast(s.len), .big);
@memcpy(bytes[3..], s);
return bytes;
}
if (s.len <= std.math.maxInt(u32)) { // str16 / bin16
const bytes = try alloc.alloc(u8, 1 + 4 + s.len);
bytes[0] = switch (kind) {
.string => 0xdb,
.binary => 0xc6,
};
std.mem.writeInt(u32, bytes[1..5], @intCast(s.len), .big);
@memcpy(bytes[5..], s);
return bytes;
}
return switch (kind) {
.string => error.StringTooLong,
.binary => error.BinaryTooLong,
};
}
pub fn serialise(alloc: std.mem.Allocator, obj: Object) SerialiseError![]u8 {
switch (obj) {
.nil => return try alloc.dupe(u8, &[_]u8{0xc0}),
.bool => |b| if (b) {
return try alloc.dupe(u8, &[_]u8{0xc3});
} else {
return try alloc.dupe(u8, &[_]u8{0xc2});
},
.integer => |i| return try serialise_int(alloc, i),
.uinteger => |u| return try serialise_uint(alloc, u),
.float32 => |f| {
const bytes = try alloc.alloc(u8, 1 + 4);
bytes[0] = 0xca;
std.mem.writeInt(u32, bytes[1..5], @bitCast(f), .big);
return bytes;
},
.float64 => |f| {
const bytes = try alloc.alloc(u8, 1 + 8);
bytes[0] = 0xcb;
std.mem.writeInt(u64, bytes[1..9], @bitCast(f), .big);
return bytes;
},
.string => |s| return try serialise_raw(alloc, s, .string),
.binary => |b| return try serialise_raw(alloc, b, .binary),
else => unreachable,
}
// const bytes = try alloc.alloc(u8, 0);
// return bytes;
}