| ... | @@ -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 | } |