|
| 1 | +//! Unpacking state. |
| 2 | +//! |
| 3 | +//! This structure manages user-supplied data and converts messagepack |
| 4 | +//! representations to the host representations. |
| 5 | +//! |
| 6 | +//! To start unpacking, you supply the data with `Unpack.init`. |
| 7 | +//! |
| 8 | +//! - `Unpack.peek` begins the unpack process for a value. |
| 9 | +//! - The function returns a `HeaderType`. |
| 10 | +//! - Use `HeaderType.nextComponentSize` to get the required data size |
| 11 | +//! for the header. |
| 12 | +//! - Ensured the `Unpack.rest` have enough data, use `Unpack.next` move to |
| 13 | +//! the next value and extract the header. |
| 14 | +//! - The result is a `Header`, it can be inspected to decide what to do with |
| 15 | +//! the current value. |
| 16 | +//! - You can consume the value with any of `Unpack.nil`, |
| 17 | +//! `Unpack.@"bool"`, `Unpack.int`, `Unpack.float`, `Unpack.array`, |
| 18 | +//! `Unpack.map`, `Unpack.raw`. |
| 19 | +//! |
| 20 | +//! You must consume the current value for the next value. To skip the |
| 21 | +//! current value, use `Unpack.raw`. `Unpack.raw` can consume any value |
| 22 | +//! as a []const u8, excepts arrays and maps. |
| 23 | +//! Because they don't have determined byte size in header, see `Header.size`. |
| 24 | +//! |
| 25 | +//! If the buffer is appended with more data, use `Unpack.setAppend` to set the |
| 26 | +//! updated buffer. |
| 27 | +//! |
| 28 | +//! This structure does not have additional internal state. You can |
| 29 | +//! save the state and return to the point as you wish. |
| 30 | +//! |
| 31 | +//! - Concurrency-safe: No |
| 32 | +//! - See `io.UnpackReader` |
| 33 | +//! |
| 34 | +//! ``` |
| 35 | +//! const unpack: Unpack = Unpack.init(data); |
| 36 | +//! |
| 37 | +//! if (unpack.peek()) |peek| { |
| 38 | +//! const requiredSize = peek.nextComponentSize(); |
| 39 | +//! if (requiredSize > unpack.rest.len) { |
| 40 | +//! const ndata = readMore(data); |
| 41 | +//! unpack.setAppend(data.len, ndata); |
| 42 | +//! data = ndata; |
| 43 | +//! } |
| 44 | +//! |
| 45 | +//! const header = unpack.next(peek); |
| 46 | +//! if ((header.type.family() != .array |
| 47 | +//! or header.type.family() != .map) // streaming map or array elements |
| 48 | +//! and unpack.rest.len < header.size) { |
| 49 | +//! const ndata = readMore(data); |
| 50 | +//! unpack.setAppend(data.len, ndata); |
| 51 | +//! data = ndata; |
| 52 | +//! } |
| 53 | +//! } else { |
| 54 | +//! doSomething(); // No enough data to peek |
| 55 | +//! } |
| 56 | +//! ``` |
| 57 | +const std = @import("std"); |
| 58 | +const compatstd = @import("./compatstd.zig"); |
| 59 | +const readIntBig = compatstd.mem.readIntBig; |
| 60 | +const root = @import("./root.zig"); |
| 61 | +const HeaderType = root.HeaderType; |
| 62 | +const Header = root.Header; |
| 63 | + |
| 64 | +rest: []const u8, |
| 65 | + |
| 66 | +const Unpack = @This(); |
| 67 | + |
| 68 | +pub fn init(data: []const u8) Unpack { |
| 69 | + return .{ .rest = data }; |
| 70 | +} |
| 71 | + |
| 72 | +pub fn setAppend(self: *Unpack, olen: usize, new: []const u8) void { |
| 73 | + const ofs = olen - self.rest.len; |
| 74 | + self.rest = new[ofs..]; |
| 75 | +} |
| 76 | + |
| 77 | +pub const PeekError = error{ |
| 78 | + BufferEmpty, |
| 79 | + UnrecognizedType, |
| 80 | +}; |
| 81 | + |
| 82 | +pub fn peek(self: *const Unpack) PeekError!HeaderType { |
| 83 | + if (self.rest.len == 0) { |
| 84 | + return PeekError.BufferEmpty; |
| 85 | + } |
| 86 | + |
| 87 | + return HeaderType.from(self.rest[0]) orelse PeekError.UnrecognizedType; |
| 88 | +} |
| 89 | + |
| 90 | +/// Consumes the current value header. |
| 91 | +/// |
| 92 | +/// You can use the result `Header.size` to confirm the |
| 93 | +/// buffer can have further read. |
| 94 | +/// Note: for array or map, the size is the number of items. |
| 95 | +/// You can ignore it for streaming or still use it to prepare data, |
| 96 | +/// this is also the number of bytes of container types. |
| 97 | +/// |
| 98 | +/// Calling this function, you must confirm the buffer has enough data to |
| 99 | +/// read. Use `HeaderType.nextComponentSize` to get the expected size for |
| 100 | +/// the value header. |
| 101 | +pub fn next(self: *Unpack, headerType: HeaderType) Header { |
| 102 | + const header, const consumes = Header.from(headerType, self.rest[1..]); |
| 103 | + self.rest = self.rest[1 + consumes ..]; |
| 104 | + return header; |
| 105 | +} |
| 106 | + |
| 107 | +pub const ConvertError = error{InvalidValue}; |
| 108 | + |
| 109 | +/// Consumes the current value as the null. |
| 110 | +/// |
| 111 | +/// The `T` must be an optional type. |
| 112 | +pub fn nil(_: *Unpack, T: type, header: Header) ConvertError!T { |
| 113 | + if (header.type == .nil) { |
| 114 | + return null; |
| 115 | + } |
| 116 | + return ConvertError.InvalidValue; |
| 117 | +} |
| 118 | + |
| 119 | +/// Consumes the current value as a bool. |
| 120 | +/// |
| 121 | +/// This function does not need additional data from the buffer. |
| 122 | +pub fn @"bool"(_: *Unpack, header: Header) ConvertError!bool { |
| 123 | + return switch (header.type) { |
| 124 | + .bool => |v| v, |
| 125 | + else => ConvertError.InvalidValue, |
| 126 | + }; |
| 127 | +} |
| 128 | + |
| 129 | +inline fn rawUInt(self: *Unpack, header: Header) ConvertError!u64 { |
| 130 | + defer self.rest = self.rest[header.size..]; |
| 131 | + return switch (header.size) { |
| 132 | + 1 => readIntBig(u8, self.rest[0..1]), |
| 133 | + 2 => readIntBig(u16, self.rest[0..2]), |
| 134 | + 4 => readIntBig(u32, self.rest[0..4]), |
| 135 | + 8 => readIntBig(u64, self.rest[0..8]), |
| 136 | + else => unreachable, |
| 137 | + }; |
| 138 | +} |
| 139 | + |
| 140 | +inline fn rawInt(self: *Unpack, header: Header) ConvertError!i64 { |
| 141 | + defer self.rest = self.rest[header.size..]; |
| 142 | + return switch (header.size) { |
| 143 | + 1 => readIntBig(i8, self.rest[0..1]), |
| 144 | + 2 => readIntBig(i16, self.rest[0..2]), |
| 145 | + 4 => readIntBig(i32, self.rest[0..4]), |
| 146 | + 8 => readIntBig(i64, self.rest[0..8]), |
| 147 | + else => unreachable, |
| 148 | + }; |
| 149 | +} |
| 150 | + |
| 151 | +/// Consume the current value and casts to your requested integer type. |
| 152 | +/// |
| 153 | +/// Use `i65` to make sure enough space for any unsigned integer. |
| 154 | +pub fn int(self: *Unpack, Int: type, header: Header) ConvertError!Int { |
| 155 | + return switch (header.type) { |
| 156 | + .fixint => |n| std.math.cast(Int, n) orelse ConvertError.InvalidValue, |
| 157 | + .int => std.math.cast(Int, try self.rawInt(header)) orelse ConvertError.InvalidValue, |
| 158 | + .uint => std.math.cast(Int, try self.rawUInt(header)) orelse ConvertError.InvalidValue, |
| 159 | + .float => @intFromFloat(try self.rawFloat(header)), |
| 160 | + else => ConvertError.InvalidValue, |
| 161 | + }; |
| 162 | +} |
| 163 | + |
| 164 | +/// Consume the current value as the raw, as long as they |
| 165 | +/// have the size. |
| 166 | +pub fn raw(self: *Unpack, header: Header) ConvertError![]const u8 { |
| 167 | + switch (header.type) { |
| 168 | + .array, .fixarray, .map, .fixmap => return ConvertError.InvalidValue, |
| 169 | + else => { |
| 170 | + const result = self.rest[0..header.size]; |
| 171 | + self.rest = self.rest[header.size..]; |
| 172 | + return result; |
| 173 | + }, |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +inline fn rawFloat(self: *Unpack, header: Header) ConvertError!f64 { |
| 178 | + const value: f64 = switch (header.size) { |
| 179 | + 4 => compatstd.mem.readFloatBig(f32, self.rest[0..4]), |
| 180 | + 8 => compatstd.mem.readFloatBig(f64, self.rest[0..8]), |
| 181 | + else => unreachable, |
| 182 | + }; |
| 183 | + self.rest = self.rest[header.size..]; |
| 184 | + return value; |
| 185 | +} |
| 186 | + |
| 187 | +fn checkedFloatFromInt(Float: type, i: anytype) ConvertError!Float { |
| 188 | + const max = (2 << (std.math.floatMantissaBits(Float) + 1)) + 1; |
| 189 | + const min = -max; |
| 190 | + |
| 191 | + if (i > max or i < min) { |
| 192 | + return ConvertError.InvalidValue; |
| 193 | + } |
| 194 | + |
| 195 | + return @floatFromInt(i); |
| 196 | +} |
| 197 | + |
| 198 | +/// Consume the current value and casts to requested float type. |
| 199 | +pub fn float(self: *Unpack, Float: type, header: Header) ConvertError!Float { |
| 200 | + return switch (header.type) { |
| 201 | + .float => @floatCast(try self.rawFloat(header)), |
| 202 | + .fixint => |n| checkedFloatFromInt(Float, n), |
| 203 | + .int => checkedFloatFromInt(Float, try self.rawInt(header)), |
| 204 | + .uint => checkedFloatFromInt(Float, try self.rawUInt(header)), |
| 205 | + else => ConvertError.InvalidValue, |
| 206 | + }; |
| 207 | +} |
| 208 | + |
| 209 | +pub fn array(self: *Unpack, header: Header) ConvertError!Array { |
| 210 | + return switch (header.type) { |
| 211 | + .fixarray, .array => Array{ |
| 212 | + .unpack = self, |
| 213 | + .len = header.size, |
| 214 | + }, |
| 215 | + else => return ConvertError.InvalidValue, |
| 216 | + }; |
| 217 | +} |
| 218 | + |
| 219 | +pub fn map(self: *Unpack, header: Header) ConvertError!Map { |
| 220 | + return switch (header.type) { |
| 221 | + .fixmap, .map => Map{ |
| 222 | + .unpack = self, |
| 223 | + .len = header.size, |
| 224 | + }, |
| 225 | + else => return ConvertError.InvalidValue, |
| 226 | + }; |
| 227 | +} |
| 228 | + |
| 229 | +pub const Array = struct { |
| 230 | + unpack: *Unpack, |
| 231 | + current: u32 = 0, |
| 232 | + len: u32, |
| 233 | + |
| 234 | + /// Peek the next header type. |
| 235 | + /// |
| 236 | + /// Return `null` if the array is ended, `PeekError.BufferEmpty` |
| 237 | + /// if the buffered data does not enough for peeking. |
| 238 | + pub fn peek(self: Array) PeekError!?HeaderType { |
| 239 | + if (self.current >= self.len) { |
| 240 | + return null; |
| 241 | + } |
| 242 | + |
| 243 | + return try self.unpack.peek(); |
| 244 | + } |
| 245 | + |
| 246 | + pub fn next(self: *Array, headerType: HeaderType) Header { |
| 247 | + const value = self.unpack.next(headerType); |
| 248 | + self.current += 1; |
| 249 | + return value; |
| 250 | + } |
| 251 | +}; |
| 252 | + |
| 253 | +pub const Map = struct { |
| 254 | + unpack: *Unpack, |
| 255 | + current: u32 = 0, |
| 256 | + len: u32, |
| 257 | + is_value: bool = false, |
| 258 | + |
| 259 | + pub fn peek(self: Map) PeekError!?HeaderType { |
| 260 | + if (self.current >= self.len) { |
| 261 | + return null; |
| 262 | + } |
| 263 | + |
| 264 | + return try self.unpack.peek(); |
| 265 | + } |
| 266 | + |
| 267 | + pub fn next(self: *Map, headerType: HeaderType) Header { |
| 268 | + const value = self.unpack.next(headerType); |
| 269 | + if (self.is_value) { |
| 270 | + self.current += 1; |
| 271 | + } |
| 272 | + self.is_value = !self.is_value; |
| 273 | + return value; |
| 274 | + } |
| 275 | +}; |
0 commit comments