diff --git a/lib/std/io/Reader.zig b/lib/std/io/Reader.zig index 33187125b8..b29e15ca59 100644 --- a/lib/std/io/Reader.zig +++ b/lib/std/io/Reader.zig @@ -324,20 +324,49 @@ pub fn isBytes(self: Self, slice: []const u8) anyerror!bool { return matches; } +/// Read a struct from the stream. +/// Only packed and extern structs are supported, as they have a defined in-memory layout. +/// Packed structs must have a `@bitSizeOf` that is a multiple of eight. pub fn readStruct(self: Self, comptime T: type) anyerror!T { - // Only extern and packed structs have defined in-memory layout. - comptime assert(@typeInfo(T).@"struct".layout != .auto); - var res: [1]T = undefined; - try self.readNoEof(mem.sliceAsBytes(res[0..])); - return res[0]; + switch (@typeInfo(T).@"struct".layout) { + .auto => @compileError("readStruct only supports packed and extern structs, " ++ + "but the given type: " ++ @typeName(T) ++ " is a normal struct."), + .@"extern" => { + var res: [1]T = undefined; + try self.readNoEof(mem.sliceAsBytes(res[0..])); + return res[0]; + }, + .@"packed" => { + var bytes: [@divExact(@bitSizeOf(T), 8)]u8 = undefined; + try self.readNoEof(&bytes); + return @bitCast(bytes); + }, + } } +/// Read a struct having the specified endianness into the host endianness representation. +/// Only packed and extern structs are supported, as they have a defined in-memory layout. +/// Packed structs must have a `@bitSizeOf` that is a multiple of eight. pub fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T { - var res = try self.readStruct(T); - if (native_endian != endian) { - mem.byteSwapAllFields(T, &res); + switch (@typeInfo(T).@"struct".layout) { + .auto => @compileError("readStructEndian only supports packed and extern structs, " ++ + "but the given type: " ++ @typeName(T) ++ " is a normal struct."), + .@"extern" => { + var res = try self.readStruct(T); + if (native_endian != endian) { + mem.byteSwapAllFields(T, &res); + } + return res; + }, + .@"packed" => { + var bytes: [@divExact(@bitSizeOf(T), 8)]u8 = undefined; + try self.readNoEof(&bytes); + if (native_endian != endian) { + mem.reverse(u8, &bytes); + } + return @bitCast(bytes); + }, } - return res; } /// Reads an integer with the same size as the given enum's tag type. If the integer matches diff --git a/lib/std/io/Reader/test.zig b/lib/std/io/Reader/test.zig index 30f0e1269c..c6250cc10f 100644 --- a/lib/std/io/Reader/test.zig +++ b/lib/std/io/Reader/test.zig @@ -1,6 +1,7 @@ const builtin = @import("builtin"); const std = @import("../../std.zig"); const testing = std.testing; +const native_endian = @import("builtin").target.cpu.arch.endian(); test "Reader" { var buf = "a\x02".*; @@ -370,3 +371,130 @@ test "readIntoBoundedBytes correctly reads into a provided bounded array" { try reader.readIntoBoundedBytes(10000, &bounded_array); try testing.expectEqualStrings(bounded_array.slice(), test_string); } + +test "readStructEndian reads packed structs without padding and in correct field order" { + const buf = [3]u8{ 11, 12, 13 }; + var fis = std.io.fixedBufferStream(&buf); + const reader = fis.reader(); + + const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 }; + + try testing.expectEqual( + PackedStruct{ .a = 11, .b = 12, .c = 13 }, + reader.readStructEndian(PackedStruct, .little), + ); + fis.reset(); + try testing.expectEqual( + PackedStruct{ .a = 13, .b = 12, .c = 11 }, + reader.readStructEndian(PackedStruct, .big), + ); +} + +test "readStruct reads packed structs without padding and in correct field order" { + const buf = [3]u8{ 11, 12, 13 }; + var fis = std.io.fixedBufferStream(&buf); + const reader = fis.reader(); + + const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 }; + + switch (native_endian) { + .little => { + try testing.expectEqual( + PackedStruct{ .a = 11, .b = 12, .c = 13 }, + reader.readStruct(PackedStruct), + ); + }, + .big => { + try testing.expectEqual( + PackedStruct{ .a = 13, .b = 12, .c = 11 }, + reader.readStruct(PackedStruct), + ); + }, + } +} + +test "readStruct writeStruct round-trip with packed structs" { + var buf: [8]u8 = undefined; + var fis = std.io.fixedBufferStream(&buf); + const reader = fis.reader(); + const writer = fis.writer(); + + const PackedStruct = packed struct(u64) { + a: u16 = 123, + b: u16 = 245, + c: u16 = 456, + d: i13 = -345, + e: i3 = 2, + }; + + const expected_packed_struct = PackedStruct{}; + try writer.writeStruct(expected_packed_struct); + fis.reset(); + try testing.expectEqual(expected_packed_struct, try reader.readStruct(PackedStruct)); +} + +test "readStructEndian writeStructEndian round-trip with packed structs" { + var buf: [8]u8 = undefined; + var fis = std.io.fixedBufferStream(&buf); + const reader = fis.reader(); + const writer = fis.writer(); + + const PackedStruct = packed struct(u64) { + a: u13 = 123, + b: i7 = -24, + c: u20 = 83, + d: enum(i24) { val = 3452 } = .val, + }; + + const expected_packed_struct = PackedStruct{}; + // round-trip little endian + try writer.writeStructEndian(expected_packed_struct, .big); + fis.reset(); + try testing.expectEqual(expected_packed_struct, try reader.readStructEndian(PackedStruct, .big)); + // round-trip big endian + fis.reset(); + try writer.writeStructEndian(expected_packed_struct, .little); + fis.reset(); + try testing.expectEqual(expected_packed_struct, try reader.readStructEndian(PackedStruct, .little)); +} + +test "readStruct a packed struct with endianness-affected types" { + const buf = [4]u8{ 0x12, 0x34, 0x56, 0x78 }; + var fis = std.io.fixedBufferStream(&buf); + const reader = fis.reader(); + + const PackedStruct = packed struct(u32) { a: u16, b: u16 }; + + switch (native_endian) { + .little => { + try testing.expectEqual( + PackedStruct{ .a = 0x3412, .b = 0x7856 }, + reader.readStruct(PackedStruct), + ); + }, + .big => { + try testing.expectEqual( + PackedStruct{ .a = 0x5678, .b = 0x1234 }, + reader.readStruct(PackedStruct), + ); + }, + } +} + +test "readStructEndian a packed struct with endianness-affected types" { + const buf = [4]u8{ 0x12, 0x34, 0x56, 0x78 }; + var fis = std.io.fixedBufferStream(&buf); + const reader = fis.reader(); + + const PackedStruct = packed struct(u32) { a: u16, b: u16 }; + + try testing.expectEqual( + PackedStruct{ .a = 0x3412, .b = 0x7856 }, + reader.readStructEndian(PackedStruct, .little), + ); + fis.reset(); + try testing.expectEqual( + PackedStruct{ .a = 0x5678, .b = 0x1234 }, + reader.readStructEndian(PackedStruct, .big), + ); +} diff --git a/lib/std/io/Writer.zig b/lib/std/io/Writer.zig index 26d4f88def..d027ecec2f 100644 --- a/lib/std/io/Writer.zig +++ b/lib/std/io/Writer.zig @@ -54,20 +54,48 @@ pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.built return self.writeAll(&bytes); } +/// Write a struct to the stream. +/// Only packed and extern structs are supported, as they have a defined in-memory layout. +/// Packed structs must have a `@bitSizeOf` that is a multiple of eight. pub fn writeStruct(self: Self, value: anytype) anyerror!void { // Only extern and packed structs have defined in-memory layout. - comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); - return self.writeAll(mem.asBytes(&value)); + switch (@typeInfo(@TypeOf(value)).@"struct".layout) { + .auto => @compileError("writeStruct only supports packed and extern structs, " ++ + "but the given type: " ++ @typeName(@TypeOf(value)) ++ " is a normal struct."), + .@"extern" => { + return try self.writeAll(mem.asBytes(&value)); + }, + .@"packed" => { + const bytes: [@divExact(@bitSizeOf(@TypeOf(value)), 8)]u8 = @bitCast(value); + try self.writeAll(&bytes); + }, + } } +/// Write a struct to the stream in the specified endianness. +/// Only packed and extern structs are supported, as they have a defined in-memory layout. +/// Packed structs must have a `@bitSizeOf` that is a multiple of eight. pub fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) anyerror!void { // TODO: make sure this value is not a reference type - if (native_endian == endian) { - return self.writeStruct(value); - } else { - var copy = value; - mem.byteSwapAllFields(@TypeOf(value), ©); - return self.writeStruct(copy); + switch (@typeInfo(@TypeOf(value)).@"struct".layout) { + .auto => @compileError("writeStructEndian only supports packed and extern structs, " ++ + "but the given type: " ++ @typeName(@TypeOf(value)) ++ " is a normal struct."), + .@"extern" => { + if (native_endian == endian) { + return try self.writeStruct(value); + } else { + var copy = value; + mem.byteSwapAllFields(@TypeOf(value), ©); + return try self.writeStruct(copy); + } + }, + .@"packed" => { + var bytes: [@divExact(@bitSizeOf(@TypeOf(value)), 8)]u8 = @bitCast(value); + if (native_endian != endian) { + mem.reverse(u8, &bytes); + } + return try self.writeAll(&bytes); + }, } } @@ -81,3 +109,7 @@ pub fn writeFile(self: Self, file: std.fs.File) anyerror!void { if (n < buf.len) return; } } + +test { + _ = @import("Writer/test.zig"); +} diff --git a/lib/std/io/Writer/test.zig b/lib/std/io/Writer/test.zig new file mode 100644 index 0000000000..2ca6013f95 --- /dev/null +++ b/lib/std/io/Writer/test.zig @@ -0,0 +1,63 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const native_endian = @import("builtin").target.cpu.arch.endian(); + +test "writeStruct writes packed structs without padding" { + var buf: [3]u8 = undefined; + var fis = std.io.fixedBufferStream(&buf); + const writer = fis.writer(); + + const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 }; + + try writer.writeStruct(PackedStruct{ .a = 11, .b = 12, .c = 13 }); + switch (native_endian) { + .little => { + try testing.expectEqualSlices(u8, &.{ 11, 12, 13 }, &buf); + }, + .big => { + try testing.expectEqualSlices(u8, &.{ 13, 12, 11 }, &buf); + }, + } +} + +test "writeStructEndian writes packed structs without padding and in correct field order" { + var buf: [3]u8 = undefined; + var fis = std.io.fixedBufferStream(&buf); + const writer = fis.writer(); + + const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 }; + + try writer.writeStructEndian(PackedStruct{ .a = 11, .b = 12, .c = 13 }, .little); + try testing.expectEqualSlices(u8, &.{ 11, 12, 13 }, &buf); + fis.reset(); + try writer.writeStructEndian(PackedStruct{ .a = 11, .b = 12, .c = 13 }, .big); + try testing.expectEqualSlices(u8, &.{ 13, 12, 11 }, &buf); +} + +test "writeStruct a packed struct with endianness-affected types" { + var buf: [4]u8 = undefined; + var fis = std.io.fixedBufferStream(&buf); + const writer = fis.writer(); + + const PackedStruct = packed struct(u32) { a: u16, b: u16 }; + + try writer.writeStruct(PackedStruct{ .a = 0x1234, .b = 0x5678 }); + switch (native_endian) { + .little => try testing.expectEqualSlices(u8, &.{ 0x34, 0x12, 0x78, 0x56 }, &buf), + .big => try testing.expectEqualSlices(u8, &.{ 0x56, 0x78, 0x12, 0x34 }, &buf), + } +} + +test "writeStructEndian a packed struct with endianness-affected types" { + var buf: [4]u8 = undefined; + var fis = std.io.fixedBufferStream(&buf); + const writer = fis.writer(); + + const PackedStruct = packed struct(u32) { a: u16, b: u16 }; + + try writer.writeStructEndian(PackedStruct{ .a = 0x1234, .b = 0x5678 }, .little); + try testing.expectEqualSlices(u8, &.{ 0x34, 0x12, 0x78, 0x56 }, &buf); + fis.reset(); + try writer.writeStructEndian(PackedStruct{ .a = 0x1234, .b = 0x5678 }, .big); + try testing.expectEqualSlices(u8, &.{ 0x56, 0x78, 0x12, 0x34 }, &buf); +}