This commit is contained in:
Jeff Anderson 2024-11-21 05:38:39 +00:00 committed by GitHub
commit 9c13b8c742
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 269 additions and 17 deletions

View File

@ -324,20 +324,49 @@ pub fn isBytes(self: Self, slice: []const u8) anyerror!bool {
return matches; 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 { pub fn readStruct(self: Self, comptime T: type) anyerror!T {
// Only extern and packed structs have defined in-memory layout. switch (@typeInfo(T).@"struct".layout) {
comptime assert(@typeInfo(T).@"struct".layout != .auto); .auto => @compileError("readStruct only supports packed and extern structs, " ++
var res: [1]T = undefined; "but the given type: " ++ @typeName(T) ++ " is a normal struct."),
try self.readNoEof(mem.sliceAsBytes(res[0..])); .@"extern" => {
return res[0]; 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 { pub fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
var res = try self.readStruct(T); switch (@typeInfo(T).@"struct".layout) {
if (native_endian != endian) { .auto => @compileError("readStructEndian only supports packed and extern structs, " ++
mem.byteSwapAllFields(T, &res); "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 /// Reads an integer with the same size as the given enum's tag type. If the integer matches

View File

@ -1,6 +1,7 @@
const builtin = @import("builtin"); const builtin = @import("builtin");
const std = @import("../../std.zig"); const std = @import("../../std.zig");
const testing = std.testing; const testing = std.testing;
const native_endian = @import("builtin").target.cpu.arch.endian();
test "Reader" { test "Reader" {
var buf = "a\x02".*; var buf = "a\x02".*;
@ -370,3 +371,130 @@ test "readIntoBoundedBytes correctly reads into a provided bounded array" {
try reader.readIntoBoundedBytes(10000, &bounded_array); try reader.readIntoBoundedBytes(10000, &bounded_array);
try testing.expectEqualStrings(bounded_array.slice(), test_string); 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),
);
}

View File

@ -54,20 +54,48 @@ pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.built
return self.writeAll(&bytes); 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 { pub fn writeStruct(self: Self, value: anytype) anyerror!void {
// Only extern and packed structs have defined in-memory layout. // Only extern and packed structs have defined in-memory layout.
comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); switch (@typeInfo(@TypeOf(value)).@"struct".layout) {
return self.writeAll(mem.asBytes(&value)); .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 { pub fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) anyerror!void {
// TODO: make sure this value is not a reference type // TODO: make sure this value is not a reference type
if (native_endian == endian) { switch (@typeInfo(@TypeOf(value)).@"struct".layout) {
return self.writeStruct(value); .auto => @compileError("writeStructEndian only supports packed and extern structs, " ++
} else { "but the given type: " ++ @typeName(@TypeOf(value)) ++ " is a normal struct."),
var copy = value; .@"extern" => {
mem.byteSwapAllFields(@TypeOf(value), &copy); if (native_endian == endian) {
return self.writeStruct(copy); return try self.writeStruct(value);
} else {
var copy = value;
mem.byteSwapAllFields(@TypeOf(value), &copy);
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; if (n < buf.len) return;
} }
} }
test {
_ = @import("Writer/test.zig");
}

View File

@ -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);
}