| ... | ... | @@ -1,616 +0,0 @@ |
| 1 | | // SPDX-License-Identifier: MIT |
| 2 | | // Copyright (c) 2015-2020 Zig Contributors |
| 3 | | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. |
| 4 | | // The MIT license requires this copyright notice to be included in all copies |
| 5 | | // and substantial portions of the software. |
| 6 | | const std = @import("../std.zig"); |
| 7 | | const builtin = std.builtin; |
| 8 | | const io = std.io; |
| 9 | | const assert = std.debug.assert; |
| 10 | | const math = std.math; |
| 11 | | const meta = std.meta; |
| 12 | | const trait = meta.trait; |
| 13 | | const testing = std.testing; |
| 14 | | |
| 15 | | pub const Packing = enum { |
| 16 | | /// Pack data to byte alignment |
| 17 | | Byte, |
| 18 | | |
| 19 | | /// Pack data to bit alignment |
| 20 | | Bit, |
| 21 | | }; |
| 22 | | |
| 23 | | /// Creates a deserializer that deserializes types from any stream. |
| 24 | | /// If `is_packed` is true, the data stream is treated as bit-packed, |
| 25 | | /// otherwise data is expected to be packed to the smallest byte. |
| 26 | | /// Types may implement a custom deserialization routine with a |
| 27 | | /// function named `deserialize` in the form of: |
| 28 | | /// ``` |
| 29 | | /// pub fn deserialize(self: *Self, deserializer: anytype) !void |
| 30 | | /// ``` |
| 31 | | /// which will be called when the deserializer is used to deserialize |
| 32 | | /// that type. It will pass a pointer to the type instance to deserialize |
| 33 | | /// into and a pointer to the deserializer struct. |
| 34 | | pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime ReaderType: type) type { |
| 35 | | return struct { |
| 36 | | in_stream: if (packing == .Bit) io.BitReader(endian, ReaderType) else ReaderType, |
| 37 | | |
| 38 | | const Self = @This(); |
| 39 | | |
| 40 | | pub fn init(in_stream: ReaderType) Self { |
| 41 | | return Self{ |
| 42 | | .in_stream = switch (packing) { |
| 43 | | .Bit => io.bitReader(endian, in_stream), |
| 44 | | .Byte => in_stream, |
| 45 | | }, |
| 46 | | }; |
| 47 | | } |
| 48 | | |
| 49 | | pub fn alignToByte(self: *Self) void { |
| 50 | | if (packing == .Byte) return; |
| 51 | | self.in_stream.alignToByte(); |
| 52 | | } |
| 53 | | |
| 54 | | //@BUG: inferred error issue. See: #1386 |
| 55 | | fn deserializeInt(self: *Self, comptime T: type) (ReaderType.Error || error{EndOfStream})!T { |
| 56 | | comptime assert(trait.is(.Int)(T) or trait.is(.Float)(T)); |
| 57 | | |
| 58 | | const u8_bit_count = 8; |
| 59 | | const t_bit_count = comptime meta.bitCount(T); |
| 60 | | |
| 61 | | const U = std.meta.Int(.unsigned, t_bit_count); |
| 62 | | const Log2U = math.Log2Int(U); |
| 63 | | const int_size = (t_bit_count + 7) / 8; |
| 64 | | |
| 65 | | if (packing == .Bit) { |
| 66 | | const result = try self.in_stream.readBitsNoEof(U, t_bit_count); |
| 67 | | return @bitCast(T, result); |
| 68 | | } |
| 69 | | |
| 70 | | var buffer: [int_size]u8 = undefined; |
| 71 | | const read_size = try self.in_stream.read(buffer[0..]); |
| 72 | | if (read_size < int_size) return error.EndOfStream; |
| 73 | | |
| 74 | | if (int_size == 1) { |
| 75 | | if (t_bit_count == 8) return @bitCast(T, buffer[0]); |
| 76 | | const PossiblySignedByte = std.meta.Int(@typeInfo(T).Int.signedness, 8); |
| 77 | | return @truncate(T, @bitCast(PossiblySignedByte, buffer[0])); |
| 78 | | } |
| 79 | | |
| 80 | | var result = @as(U, 0); |
| 81 | | for (buffer) |byte, i| { |
| 82 | | switch (endian) { |
| 83 | | .Big => { |
| 84 | | result = (result << u8_bit_count) | byte; |
| 85 | | }, |
| 86 | | .Little => { |
| 87 | | result |= @as(U, byte) << @intCast(Log2U, u8_bit_count * i); |
| 88 | | }, |
| 89 | | } |
| 90 | | } |
| 91 | | |
| 92 | | return @bitCast(T, result); |
| 93 | | } |
| 94 | | |
| 95 | | /// Deserializes and returns data of the specified type from the stream |
| 96 | | pub fn deserialize(self: *Self, comptime T: type) !T { |
| 97 | | var value: T = undefined; |
| 98 | | try self.deserializeInto(&value); |
| 99 | | return value; |
| 100 | | } |
| 101 | | |
| 102 | | /// Deserializes data into the type pointed to by `ptr` |
| 103 | | pub fn deserializeInto(self: *Self, ptr: anytype) !void { |
| 104 | | const T = @TypeOf(ptr); |
| 105 | | comptime assert(trait.is(.Pointer)(T)); |
| 106 | | |
| 107 | | if (comptime trait.isSlice(T) or comptime trait.isPtrTo(.Array)(T)) { |
| 108 | | for (ptr) |*v| |
| 109 | | try self.deserializeInto(v); |
| 110 | | return; |
| 111 | | } |
| 112 | | |
| 113 | | comptime assert(trait.isSingleItemPtr(T)); |
| 114 | | |
| 115 | | const C = comptime meta.Child(T); |
| 116 | | const child_type_id = @typeInfo(C); |
| 117 | | |
| 118 | | //custom deserializer: fn(self: *Self, deserializer: anytype) !void |
| 119 | | if (comptime trait.hasFn("deserialize")(C)) return C.deserialize(ptr, self); |
| 120 | | |
| 121 | | if (comptime trait.isPacked(C) and packing != .Bit) { |
| 122 | | var packed_deserializer = deserializer(endian, .Bit, self.in_stream); |
| 123 | | return packed_deserializer.deserializeInto(ptr); |
| 124 | | } |
| 125 | | |
| 126 | | switch (child_type_id) { |
| 127 | | .Void => return, |
| 128 | | .Bool => ptr.* = (try self.deserializeInt(u1)) > 0, |
| 129 | | .Float, .Int => ptr.* = try self.deserializeInt(C), |
| 130 | | .Struct => { |
| 131 | | const info = @typeInfo(C).Struct; |
| 132 | | |
| 133 | | inline for (info.fields) |*field_info| { |
| 134 | | const name = field_info.name; |
| 135 | | const FieldType = field_info.field_type; |
| 136 | | |
| 137 | | if (FieldType == void or FieldType == u0) continue; |
| 138 | | |
| 139 | | //it doesn't make any sense to read pointers |
| 140 | | if (comptime trait.is(.Pointer)(FieldType)) { |
| 141 | | @compileError("Will not " ++ "read field " ++ name ++ " of struct " ++ |
| 142 | | @typeName(C) ++ " because it " ++ "is of pointer-type " ++ |
| 143 | | @typeName(FieldType) ++ "."); |
| 144 | | } |
| 145 | | |
| 146 | | try self.deserializeInto(&@field(ptr, name)); |
| 147 | | } |
| 148 | | }, |
| 149 | | .Union => { |
| 150 | | const info = @typeInfo(C).Union; |
| 151 | | if (info.tag_type) |TagType| { |
| 152 | | //we avoid duplicate iteration over the enum tags |
| 153 | | // by getting the int directly and casting it without |
| 154 | | // safety. If it is bad, it will be caught anyway. |
| 155 | | const TagInt = @TagType(TagType); |
| 156 | | const tag = try self.deserializeInt(TagInt); |
| 157 | | |
| 158 | | inline for (info.fields) |field_info| { |
| 159 | | if (@enumToInt(@field(TagType, field_info.name)) == tag) { |
| 160 | | const name = field_info.name; |
| 161 | | const FieldType = field_info.field_type; |
| 162 | | ptr.* = @unionInit(C, name, undefined); |
| 163 | | try self.deserializeInto(&@field(ptr, name)); |
| 164 | | return; |
| 165 | | } |
| 166 | | } |
| 167 | | //This is reachable if the enum data is bad |
| 168 | | return error.InvalidEnumTag; |
| 169 | | } |
| 170 | | @compileError("Cannot meaningfully deserialize " ++ @typeName(C) ++ |
| 171 | | " because it is an untagged union. Use a custom deserialize()."); |
| 172 | | }, |
| 173 | | .Optional => { |
| 174 | | const OC = comptime meta.Child(C); |
| 175 | | const exists = (try self.deserializeInt(u1)) > 0; |
| 176 | | if (!exists) { |
| 177 | | ptr.* = null; |
| 178 | | return; |
| 179 | | } |
| 180 | | |
| 181 | | ptr.* = @as(OC, undefined); //make it non-null so the following .? is guaranteed safe |
| 182 | | const val_ptr = &ptr.*.?; |
| 183 | | try self.deserializeInto(val_ptr); |
| 184 | | }, |
| 185 | | .Enum => { |
| 186 | | var value = try self.deserializeInt(@TagType(C)); |
| 187 | | ptr.* = try meta.intToEnum(C, value); |
| 188 | | }, |
| 189 | | else => { |
| 190 | | @compileError("Cannot deserialize " ++ @tagName(child_type_id) ++ " types (unimplemented)."); |
| 191 | | }, |
| 192 | | } |
| 193 | | } |
| 194 | | }; |
| 195 | | } |
| 196 | | |
| 197 | | pub fn deserializer( |
| 198 | | comptime endian: builtin.Endian, |
| 199 | | comptime packing: Packing, |
| 200 | | in_stream: anytype, |
| 201 | | ) Deserializer(endian, packing, @TypeOf(in_stream)) { |
| 202 | | return Deserializer(endian, packing, @TypeOf(in_stream)).init(in_stream); |
| 203 | | } |
| 204 | | |
| 205 | | /// Creates a serializer that serializes types to any stream. |
| 206 | | /// If `is_packed` is true, the data will be bit-packed into the stream. |
| 207 | | /// Note that the you must call `serializer.flush()` when you are done |
| 208 | | /// writing bit-packed data in order ensure any unwritten bits are committed. |
| 209 | | /// If `is_packed` is false, data is packed to the smallest byte. In the case |
| 210 | | /// of packed structs, the struct will written bit-packed and with the specified |
| 211 | | /// endianess, after which data will resume being written at the next byte boundary. |
| 212 | | /// Types may implement a custom serialization routine with a |
| 213 | | /// function named `serialize` in the form of: |
| 214 | | /// ``` |
| 215 | | /// pub fn serialize(self: Self, serializer: anytype) !void |
| 216 | | /// ``` |
| 217 | | /// which will be called when the serializer is used to serialize that type. It will |
| 218 | | /// pass a const pointer to the type instance to be serialized and a pointer |
| 219 | | /// to the serializer struct. |
| 220 | | pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime OutStreamType: type) type { |
| 221 | | return struct { |
| 222 | | out_stream: if (packing == .Bit) io.BitOutStream(endian, OutStreamType) else OutStreamType, |
| 223 | | |
| 224 | | const Self = @This(); |
| 225 | | pub const Error = OutStreamType.Error; |
| 226 | | |
| 227 | | pub fn init(out_stream: OutStreamType) Self { |
| 228 | | return Self{ |
| 229 | | .out_stream = switch (packing) { |
| 230 | | .Bit => io.bitOutStream(endian, out_stream), |
| 231 | | .Byte => out_stream, |
| 232 | | }, |
| 233 | | }; |
| 234 | | } |
| 235 | | |
| 236 | | /// Flushes any unwritten bits to the stream |
| 237 | | pub fn flush(self: *Self) Error!void { |
| 238 | | if (packing == .Bit) return self.out_stream.flushBits(); |
| 239 | | } |
| 240 | | |
| 241 | | fn serializeInt(self: *Self, value: anytype) Error!void { |
| 242 | | const T = @TypeOf(value); |
| 243 | | comptime assert(trait.is(.Int)(T) or trait.is(.Float)(T)); |
| 244 | | |
| 245 | | const t_bit_count = comptime meta.bitCount(T); |
| 246 | | const u8_bit_count = comptime meta.bitCount(u8); |
| 247 | | |
| 248 | | const U = std.meta.Int(.unsigned, t_bit_count); |
| 249 | | const Log2U = math.Log2Int(U); |
| 250 | | const int_size = (t_bit_count + 7) / 8; |
| 251 | | |
| 252 | | const u_value = @bitCast(U, value); |
| 253 | | |
| 254 | | if (packing == .Bit) return self.out_stream.writeBits(u_value, t_bit_count); |
| 255 | | |
| 256 | | var buffer: [int_size]u8 = undefined; |
| 257 | | if (int_size == 1) buffer[0] = u_value; |
| 258 | | |
| 259 | | for (buffer) |*byte, i| { |
| 260 | | const idx = switch (endian) { |
| 261 | | .Big => int_size - i - 1, |
| 262 | | .Little => i, |
| 263 | | }; |
| 264 | | const shift = @intCast(Log2U, idx * u8_bit_count); |
| 265 | | const v = u_value >> shift; |
| 266 | | byte.* = if (t_bit_count < u8_bit_count) v else @truncate(u8, v); |
| 267 | | } |
| 268 | | |
| 269 | | try self.out_stream.writeAll(&buffer); |
| 270 | | } |
| 271 | | |
| 272 | | /// Serializes the passed value into the stream |
| 273 | | pub fn serialize(self: *Self, value: anytype) Error!void { |
| 274 | | const T = comptime @TypeOf(value); |
| 275 | | |
| 276 | | if (comptime trait.isIndexable(T)) { |
| 277 | | for (value) |v| |
| 278 | | try self.serialize(v); |
| 279 | | return; |
| 280 | | } |
| 281 | | |
| 282 | | //custom serializer: fn(self: Self, serializer: anytype) !void |
| 283 | | if (comptime trait.hasFn("serialize")(T)) return T.serialize(value, self); |
| 284 | | |
| 285 | | if (comptime trait.isPacked(T) and packing != .Bit) { |
| 286 | | var packed_serializer = Serializer(endian, .Bit, OutStreamType).init(self.out_stream); |
| 287 | | try packed_serializer.serialize(value); |
| 288 | | try packed_serializer.flush(); |
| 289 | | return; |
| 290 | | } |
| 291 | | |
| 292 | | switch (@typeInfo(T)) { |
| 293 | | .Void => return, |
| 294 | | .Bool => try self.serializeInt(@as(u1, @boolToInt(value))), |
| 295 | | .Float, .Int => try self.serializeInt(value), |
| 296 | | .Struct => { |
| 297 | | const info = @typeInfo(T); |
| 298 | | |
| 299 | | inline for (info.Struct.fields) |*field_info| { |
| 300 | | const name = field_info.name; |
| 301 | | const FieldType = field_info.field_type; |
| 302 | | |
| 303 | | if (FieldType == void or FieldType == u0) continue; |
| 304 | | |
| 305 | | //It doesn't make sense to write pointers |
| 306 | | if (comptime trait.is(.Pointer)(FieldType)) { |
| 307 | | @compileError("Will not " ++ "serialize field " ++ name ++ |
| 308 | | " of struct " ++ @typeName(T) ++ " because it " ++ |
| 309 | | "is of pointer-type " ++ @typeName(FieldType) ++ "."); |
| 310 | | } |
| 311 | | try self.serialize(@field(value, name)); |
| 312 | | } |
| 313 | | }, |
| 314 | | .Union => { |
| 315 | | const info = @typeInfo(T).Union; |
| 316 | | if (info.tag_type) |TagType| { |
| 317 | | const active_tag = meta.activeTag(value); |
| 318 | | try self.serialize(active_tag); |
| 319 | | //This inline loop is necessary because active_tag is a runtime |
| 320 | | // value, but @field requires a comptime value. Our alternative |
| 321 | | // is to check each field for a match |
| 322 | | inline for (info.fields) |field_info| { |
| 323 | | if (@field(TagType, field_info.name) == active_tag) { |
| 324 | | const name = field_info.name; |
| 325 | | const FieldType = field_info.field_type; |
| 326 | | try self.serialize(@field(value, name)); |
| 327 | | return; |
| 328 | | } |
| 329 | | } |
| 330 | | unreachable; |
| 331 | | } |
| 332 | | @compileError("Cannot meaningfully serialize " ++ @typeName(T) ++ |
| 333 | | " because it is an untagged union. Use a custom serialize()."); |
| 334 | | }, |
| 335 | | .Optional => { |
| 336 | | if (value == null) { |
| 337 | | try self.serializeInt(@as(u1, @boolToInt(false))); |
| 338 | | return; |
| 339 | | } |
| 340 | | try self.serializeInt(@as(u1, @boolToInt(true))); |
| 341 | | |
| 342 | | const OC = comptime meta.Child(T); |
| 343 | | const val_ptr = &value.?; |
| 344 | | try self.serialize(val_ptr.*); |
| 345 | | }, |
| 346 | | .Enum => { |
| 347 | | try self.serializeInt(@enumToInt(value)); |
| 348 | | }, |
| 349 | | else => @compileError("Cannot serialize " ++ @tagName(@typeInfo(T)) ++ " types (unimplemented)."), |
| 350 | | } |
| 351 | | } |
| 352 | | }; |
| 353 | | } |
| 354 | | |
| 355 | | pub fn serializer( |
| 356 | | comptime endian: builtin.Endian, |
| 357 | | comptime packing: Packing, |
| 358 | | out_stream: anytype, |
| 359 | | ) Serializer(endian, packing, @TypeOf(out_stream)) { |
| 360 | | return Serializer(endian, packing, @TypeOf(out_stream)).init(out_stream); |
| 361 | | } |
| 362 | | |
| 363 | | fn testIntSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { |
| 364 | | @setEvalBranchQuota(1500); |
| 365 | | //@NOTE: if this test is taking too long, reduce the maximum tested bitsize |
| 366 | | const max_test_bitsize = 128; |
| 367 | | |
| 368 | | const total_bytes = comptime blk: { |
| 369 | | var bytes = 0; |
| 370 | | comptime var i = 0; |
| 371 | | while (i <= max_test_bitsize) : (i += 1) bytes += (i / 8) + @boolToInt(i % 8 > 0); |
| 372 | | break :blk bytes * 2; |
| 373 | | }; |
| 374 | | |
| 375 | | var data_mem: [total_bytes]u8 = undefined; |
| 376 | | var out = io.fixedBufferStream(&data_mem); |
| 377 | | var _serializer = serializer(endian, packing, out.outStream()); |
| 378 | | |
| 379 | | var in = io.fixedBufferStream(&data_mem); |
| 380 | | var _deserializer = deserializer(endian, packing, in.reader()); |
| 381 | | |
| 382 | | comptime var i = 0; |
| 383 | | inline while (i <= max_test_bitsize) : (i += 1) { |
| 384 | | const U = std.meta.Int(.unsigned, i); |
| 385 | | const S = std.meta.Int(.signed, i); |
| 386 | | try _serializer.serializeInt(@as(U, i)); |
| 387 | | if (i != 0) try _serializer.serializeInt(@as(S, -1)) else try _serializer.serialize(@as(S, 0)); |
| 388 | | } |
| 389 | | try _serializer.flush(); |
| 390 | | |
| 391 | | i = 0; |
| 392 | | inline while (i <= max_test_bitsize) : (i += 1) { |
| 393 | | const U = std.meta.Int(.unsigned, i); |
| 394 | | const S = std.meta.Int(.signed, i); |
| 395 | | const x = try _deserializer.deserializeInt(U); |
| 396 | | const y = try _deserializer.deserializeInt(S); |
| 397 | | testing.expect(x == @as(U, i)); |
| 398 | | if (i != 0) testing.expect(y == @as(S, -1)) else testing.expect(y == 0); |
| 399 | | } |
| 400 | | |
| 401 | | const u8_bit_count = comptime meta.bitCount(u8); |
| 402 | | //0 + 1 + 2 + ... n = (n * (n + 1)) / 2 |
| 403 | | //and we have each for unsigned and signed, so * 2 |
| 404 | | const total_bits = (max_test_bitsize * (max_test_bitsize + 1)); |
| 405 | | const extra_packed_byte = @boolToInt(total_bits % u8_bit_count > 0); |
| 406 | | const total_packed_bytes = (total_bits / u8_bit_count) + extra_packed_byte; |
| 407 | | |
| 408 | | testing.expect(in.pos == if (packing == .Bit) total_packed_bytes else total_bytes); |
| 409 | | |
| 410 | | //Verify that empty error set works with serializer. |
| 411 | | //deserializer is covered by FixedBufferStream |
| 412 | | var null_serializer = io.serializer(endian, packing, std.io.null_out_stream); |
| 413 | | try null_serializer.serialize(data_mem[0..]); |
| 414 | | try null_serializer.flush(); |
| 415 | | } |
| 416 | | |
| 417 | | test "Serializer/Deserializer Int" { |
| 418 | | try testIntSerializerDeserializer(.Big, .Byte); |
| 419 | | try testIntSerializerDeserializer(.Little, .Byte); |
| 420 | | // TODO these tests are disabled due to tripping an LLVM assertion |
| 421 | | // https://github.com/ziglang/zig/issues/2019 |
| 422 | | //try testIntSerializerDeserializer(builtin.Endian.Big, true); |
| 423 | | //try testIntSerializerDeserializer(builtin.Endian.Little, true); |
| 424 | | } |
| 425 | | |
| 426 | | fn testIntSerializerDeserializerInfNaN( |
| 427 | | comptime endian: builtin.Endian, |
| 428 | | comptime packing: io.Packing, |
| 429 | | ) !void { |
| 430 | | const mem_size = (16 * 2 + 32 * 2 + 64 * 2 + 128 * 2) / comptime meta.bitCount(u8); |
| 431 | | var data_mem: [mem_size]u8 = undefined; |
| 432 | | |
| 433 | | var out = io.fixedBufferStream(&data_mem); |
| 434 | | var _serializer = serializer(endian, packing, out.outStream()); |
| 435 | | |
| 436 | | var in = io.fixedBufferStream(&data_mem); |
| 437 | | var _deserializer = deserializer(endian, packing, in.reader()); |
| 438 | | |
| 439 | | //@TODO: isInf/isNan not currently implemented for f128. |
| 440 | | try _serializer.serialize(std.math.nan(f16)); |
| 441 | | try _serializer.serialize(std.math.inf(f16)); |
| 442 | | try _serializer.serialize(std.math.nan(f32)); |
| 443 | | try _serializer.serialize(std.math.inf(f32)); |
| 444 | | try _serializer.serialize(std.math.nan(f64)); |
| 445 | | try _serializer.serialize(std.math.inf(f64)); |
| 446 | | //try serializer.serialize(std.math.nan(f128)); |
| 447 | | //try serializer.serialize(std.math.inf(f128)); |
| 448 | | const nan_check_f16 = try _deserializer.deserialize(f16); |
| 449 | | const inf_check_f16 = try _deserializer.deserialize(f16); |
| 450 | | const nan_check_f32 = try _deserializer.deserialize(f32); |
| 451 | | _deserializer.alignToByte(); |
| 452 | | const inf_check_f32 = try _deserializer.deserialize(f32); |
| 453 | | const nan_check_f64 = try _deserializer.deserialize(f64); |
| 454 | | const inf_check_f64 = try _deserializer.deserialize(f64); |
| 455 | | //const nan_check_f128 = try deserializer.deserialize(f128); |
| 456 | | //const inf_check_f128 = try deserializer.deserialize(f128); |
| 457 | | testing.expect(std.math.isNan(nan_check_f16)); |
| 458 | | testing.expect(std.math.isInf(inf_check_f16)); |
| 459 | | testing.expect(std.math.isNan(nan_check_f32)); |
| 460 | | testing.expect(std.math.isInf(inf_check_f32)); |
| 461 | | testing.expect(std.math.isNan(nan_check_f64)); |
| 462 | | testing.expect(std.math.isInf(inf_check_f64)); |
| 463 | | //expect(std.math.isNan(nan_check_f128)); |
| 464 | | //expect(std.math.isInf(inf_check_f128)); |
| 465 | | } |
| 466 | | |
| 467 | | test "Serializer/Deserializer Int: Inf/NaN" { |
| 468 | | try testIntSerializerDeserializerInfNaN(.Big, .Byte); |
| 469 | | try testIntSerializerDeserializerInfNaN(.Little, .Byte); |
| 470 | | try testIntSerializerDeserializerInfNaN(.Big, .Bit); |
| 471 | | try testIntSerializerDeserializerInfNaN(.Little, .Bit); |
| 472 | | } |
| 473 | | |
| 474 | | fn testAlternateSerializer(self: anytype, _serializer: anytype) !void { |
| 475 | | try _serializer.serialize(self.f_f16); |
| 476 | | } |
| 477 | | |
| 478 | | fn testSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { |
| 479 | | const ColorType = enum(u4) { |
| 480 | | RGB8 = 1, |
| 481 | | RA16 = 2, |
| 482 | | R32 = 3, |
| 483 | | }; |
| 484 | | |
| 485 | | const TagAlign = union(enum(u32)) { |
| 486 | | A: u8, |
| 487 | | B: u8, |
| 488 | | C: u8, |
| 489 | | }; |
| 490 | | |
| 491 | | const Color = union(ColorType) { |
| 492 | | RGB8: struct { |
| 493 | | r: u8, |
| 494 | | g: u8, |
| 495 | | b: u8, |
| 496 | | a: u8, |
| 497 | | }, |
| 498 | | RA16: struct { |
| 499 | | r: u16, |
| 500 | | a: u16, |
| 501 | | }, |
| 502 | | R32: u32, |
| 503 | | }; |
| 504 | | |
| 505 | | const PackedStruct = packed struct { |
| 506 | | f_i3: i3, |
| 507 | | f_u2: u2, |
| 508 | | }; |
| 509 | | |
| 510 | | //to test custom serialization |
| 511 | | const Custom = struct { |
| 512 | | f_f16: f16, |
| 513 | | f_unused_u32: u32, |
| 514 | | |
| 515 | | pub fn deserialize(self: *@This(), _deserializer: anytype) !void { |
| 516 | | try _deserializer.deserializeInto(&self.f_f16); |
| 517 | | self.f_unused_u32 = 47; |
| 518 | | } |
| 519 | | |
| 520 | | pub const serialize = testAlternateSerializer; |
| 521 | | }; |
| 522 | | |
| 523 | | const MyStruct = struct { |
| 524 | | f_i3: i3, |
| 525 | | f_u8: u8, |
| 526 | | f_tag_align: TagAlign, |
| 527 | | f_u24: u24, |
| 528 | | f_i19: i19, |
| 529 | | f_void: void, |
| 530 | | f_f32: f32, |
| 531 | | f_f128: f128, |
| 532 | | f_packed_0: PackedStruct, |
| 533 | | f_i7arr: [10]i7, |
| 534 | | f_of64n: ?f64, |
| 535 | | f_of64v: ?f64, |
| 536 | | f_color_type: ColorType, |
| 537 | | f_packed_1: PackedStruct, |
| 538 | | f_custom: Custom, |
| 539 | | f_color: Color, |
| 540 | | }; |
| 541 | | |
| 542 | | const my_inst = MyStruct{ |
| 543 | | .f_i3 = -1, |
| 544 | | .f_u8 = 8, |
| 545 | | .f_tag_align = TagAlign{ .B = 148 }, |
| 546 | | .f_u24 = 24, |
| 547 | | .f_i19 = 19, |
| 548 | | .f_void = {}, |
| 549 | | .f_f32 = 32.32, |
| 550 | | .f_f128 = 128.128, |
| 551 | | .f_packed_0 = PackedStruct{ .f_i3 = -1, .f_u2 = 2 }, |
| 552 | | .f_i7arr = [10]i7{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, |
| 553 | | .f_of64n = null, |
| 554 | | .f_of64v = 64.64, |
| 555 | | .f_color_type = ColorType.R32, |
| 556 | | .f_packed_1 = PackedStruct{ .f_i3 = 1, .f_u2 = 1 }, |
| 557 | | .f_custom = Custom{ .f_f16 = 38.63, .f_unused_u32 = 47 }, |
| 558 | | .f_color = Color{ .R32 = 123822 }, |
| 559 | | }; |
| 560 | | |
| 561 | | var data_mem: [@sizeOf(MyStruct)]u8 = undefined; |
| 562 | | var out = io.fixedBufferStream(&data_mem); |
| 563 | | var _serializer = serializer(endian, packing, out.outStream()); |
| 564 | | |
| 565 | | var in = io.fixedBufferStream(&data_mem); |
| 566 | | var _deserializer = deserializer(endian, packing, in.reader()); |
| 567 | | |
| 568 | | try _serializer.serialize(my_inst); |
| 569 | | |
| 570 | | const my_copy = try _deserializer.deserialize(MyStruct); |
| 571 | | testing.expect(meta.eql(my_copy, my_inst)); |
| 572 | | } |
| 573 | | |
| 574 | | test "Serializer/Deserializer generic" { |
| 575 | | try testSerializerDeserializer(builtin.Endian.Big, .Byte); |
| 576 | | try testSerializerDeserializer(builtin.Endian.Little, .Byte); |
| 577 | | try testSerializerDeserializer(builtin.Endian.Big, .Bit); |
| 578 | | try testSerializerDeserializer(builtin.Endian.Little, .Bit); |
| 579 | | } |
| 580 | | |
| 581 | | fn testBadData(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { |
| 582 | | const E = enum(u14) { |
| 583 | | One = 1, |
| 584 | | Two = 2, |
| 585 | | }; |
| 586 | | |
| 587 | | const A = struct { |
| 588 | | e: E, |
| 589 | | }; |
| 590 | | |
| 591 | | const C = union(E) { |
| 592 | | One: u14, |
| 593 | | Two: f16, |
| 594 | | }; |
| 595 | | |
| 596 | | var data_mem: [4]u8 = undefined; |
| 597 | | var out = io.fixedBufferStream(&data_mem); |
| 598 | | var _serializer = serializer(endian, packing, out.outStream()); |
| 599 | | |
| 600 | | var in = io.fixedBufferStream(&data_mem); |
| 601 | | var _deserializer = deserializer(endian, packing, in.reader()); |
| 602 | | |
| 603 | | try _serializer.serialize(@as(u14, 3)); |
| 604 | | testing.expectError(error.InvalidEnumTag, _deserializer.deserialize(A)); |
| 605 | | out.pos = 0; |
| 606 | | try _serializer.serialize(@as(u14, 3)); |
| 607 | | try _serializer.serialize(@as(u14, 88)); |
| 608 | | testing.expectError(error.InvalidEnumTag, _deserializer.deserialize(C)); |
| 609 | | } |
| 610 | | |
| 611 | | test "Deserializer bad data" { |
| 612 | | try testBadData(.Big, .Byte); |
| 613 | | try testBadData(.Little, .Byte); |
| 614 | | try testBadData(.Big, .Bit); |
| 615 | | try testBadData(.Little, .Bit); |
| 616 | | } |