diff --git a/lib/std/io/Reader.zig b/lib/std/io/Reader.zig index 33187125b86b..7bf474b00281 100644 --- a/lib/std/io/Reader.zig +++ b/lib/std/io/Reader.zig @@ -324,20 +324,47 @@ 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. +/// 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."), + .@"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. +/// 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."), + .@"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 30f0e1269c32..2e43214b14f6 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,44 @@ 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.expectEqualDeep( + PackedStruct{ .a = 11, .b = 12, .c = 13 }, + reader.readStructEndian(PackedStruct, .little), + ); + fis.reset(); + try testing.expectEqualDeep( + 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.expectEqualDeep( + PackedStruct{ .a = 11, .b = 12, .c = 13 }, + reader.readStruct(PackedStruct), + ); + }, + .big => { + try testing.expectEqualDeep( + PackedStruct{ .a = 13, .b = 12, .c = 11 }, + reader.readStruct(PackedStruct), + ); + }, + } +} diff --git a/lib/std/io/Writer.zig b/lib/std/io/Writer.zig index 26d4f88def80..ca8979ed74e6 100644 --- a/lib/std/io/Writer.zig +++ b/lib/std/io/Writer.zig @@ -54,20 +54,46 @@ 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. +/// 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."), + .@"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. +/// 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."), + .@"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 +107,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 000000000000..01ba4516732c --- /dev/null +++ b/lib/std/io/Writer/test.zig @@ -0,0 +1,35 @@ +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); +}