kj4tmp
August 4, 2024, 4:16am
1
I’m trying to use a binary protocol in zig. I have defined the structure of the binary protocol largely as packed structs. Is there a less verbose way to make sure I always send the data as little endian?
test "send command" {
const Command = packed struct(u16) {
flag: bool,
reserved: u7 = 0,
num: u8,
};
const my_command = Command{
.flag = false,
.num = 7,
};
var command_bytes: [2]u8 = undefined;
var fbs = std.io.fixedBufferStream(command_bytes);
fbs.writer().writeInt(
u16,
@bitCast(my_command),
std.builtin.Endian.little,
);
send_command(&command_bytes);
}
Currently my send_command is like socket send
, it accepts a slice of bytes to send.
Some difficulties:
The packed structs will always be a multiple of 8 bits long, but I cannot use std.mem.asBytes since that will give me padding bytes.
I need to send many different types of packed structs easily.
I need to always serialize as little endian, regardless of host endianness.
I don’t want endian handling littered all over my codebase.
kj4tmp
August 4, 2024, 7:56am
2
I came up with this helper function:
const native_endian = @import("builtin").target.cpu.arch.endian();
fn packed_struct_to_bytes_little(comptime T: type, packed_struct: T) [@divExact(@bitSizeOf(T), 8)]u8 {
comptime std.debug.assert(@typeInfo(T).Struct.layout == .@"packed"); // must be a packed struct
var bytes: [@divExact(@bitSizeOf(T), 8)]u8 = undefined;
if (native_endian == .little) {
bytes = @bitCast(packed_struct);
} else {
std.mem.writePackedInt(
@typeInfo(T).Struct.backing_integer.?,
&bytes,
0,
@bitCast(packed_struct),
.little,
);
}
return bytes;
}
Not entirely sure if I’m doing this correctly or if there is an easier way.
But if you’re writing to a buffer using writeInt
, you’re going to write the padding as well.
I think what you want is this:
pub fn send(cmd: anytype) void{
const Cmd = @TypeOf(cmd);
const Backing = @typeInfo(Cmd).Struct.backing_integer.?;
const realBits = @typeInfo(Backing).Int.bits;
const realBytes = @divExact(realBits, 8);
const native_endian = @import("builtin").target.cpu.arch.endian();
switch(native_endian){
.little => {
const bytes = std.mem.asBytes(&cmd);
sendBytes(bytes[0..realBytes]);
},
.big => {
const temp = @byteSwap(cmd);
const bytes = std.mem.asBytes(&temp);
const padding = @sizeOf(Cmd) - realBytes;
sendBytes(bytes[padding..]);
},
}
}
2 Likes
kj4tmp
August 4, 2024, 8:23am
4
I am using writePackedInt
which shouldn’t write padding, I think.
Also, @byteswap
only accepts integers or integer vector types.
Right. You can bitcast it to the backing integer.