/// Type tag for Message union. /// This is the first value in the actual packet sent over the network. pub const PacketType = enum(u16) { relay = 0x003C, file_transfer = 0x8888, connection = 0x00E9, _, }; /// Reserved option values. /// Currently unused. pub const ConnectionOptions = packed struct(u8) { opt1: bool = false, opt2: bool = false, opt3: bool = false, opt4: bool = false, opt5: bool = false, opt6: bool = false, opt7: bool = false, opt8: bool = false, }; pub const Error = error{ NotImplementedSaprusType, UnknownSaprusType, InvalidMessage, }; pub fn MessageNew(comptime packet_type: PacketType) type { comptime { if (packet_type == .file_transfer) @compileError("File transfer not implemented"); if (packet_type != .relay and packet_type != .connection) @compileError("Unkown message type"); } return packed struct { const Self = @This(); const SelfBytes = []align(@alignOf(Self)) u8; const Relay = struct { pub fn getPayload(self: *Self) []u8 { return @as([*]align(@alignOf(Self)) u8, @ptrCast(&self.payload))[0 .. self.length - 4]; } }; const Connection = packed struct { pub fn getPayload(self: Self) []u8 { return @as([*]u8, &self.payload)[0 .. self.length - 4]; } }; type: PacketType = packet_type, length: u16, // Relay dest: if (packet_type == .relay) @Vector(4, u8) else void, // Connection src_port: if (packet_type == .connection) u16 else void, // random number > 1024 dest_port: if (packet_type == .connection) u16 else void, // random number > 1024 seq_num: if (packet_type == .connection) u32 else void, msg_id: if (packet_type == .connection) u32 else void, reserved: if (packet_type == .connection) u8 else void, options: if (packet_type == .connection) ConnectionOptions else void = if (packet_type == .connection) .{} else {}, // Relay or Connection payload: switch (packet_type) { .relay, .connection => void, else => noreturn, }, pub usingnamespace switch (packet_type) { .relay => Relay, .connection => Connection, .file_transfer => @compileError("File Transfer message type not implemented"), else => @compileError("Unknown message type"), }; pub fn init(allocator: Allocator, payload_len: u16) !*Self { const size = payload_len + @sizeOf(Self); const bytes = try allocator.alignedAlloc(u8, @alignOf(Self), size); const res: *Self = @ptrCast(bytes.ptr); res.type = packet_type; res.length = payload_len; return res; } pub fn deinit(self: *Self, allocator: Allocator) void { allocator.free(self.asBytes()); } pub fn nativeFromNetworkEndian(self: *Self) void { self.type = @enumFromInt(bigToNative( @typeInfo(@TypeOf(self.type)).@"enum".tag_type, @intFromEnum(self.type), )); self.length = bigToNative(@TypeOf(self.length), self.length); if (packet_type == .connection) { self.src_port = bigToNative(@TypeOf(self.src_port), self.src_port); self.dest_port = bigToNative(@TypeOf(self.dest_port), self.dest_port); self.seq_num = bigToNative(@TypeOf(self.seq_num), self.seq_num); self.msg_id = bigToNative(@TypeOf(self.msg_id), self.msg_id); } } pub fn networkFromNativeEndian(self: *Self) void { self.type = @enumFromInt(bigToNative( @typeInfo(@TypeOf(self.type)).@"enum".tag_type, @intFromEnum(self.type), )); self.length = bigToNative(@TypeOf(self.length), self.length); if (packet_type == .connection) { self.src_port = nativeToBig(@TypeOf(self.src_port), self.src_port); self.dest_port = nativeToBig(@TypeOf(self.dest_port), self.dest_port); self.seq_num = nativeToBig(@TypeOf(self.seq_num), self.seq_num); self.msg_id = nativeToBig(@TypeOf(self.msg_id), self.msg_id); } } pub fn asBytes(self: *Self) SelfBytes { const size = @sizeOf(Self) + self.length; return @as([*]align(@alignOf(Self)) u8, @ptrCast(self))[0..size]; } }; } test MessageNew { comptime for (@typeInfo(MessageNew(.connection)).@"struct".decls) |field| { @compileLog(field); }; } // pub fn bytesAsMessage(bytes: []const u8) !*Self { // const res = std.mem.bytesAsValue(Self, bytes); // return switch (res.type) { // .relay, .connection => if (bytes.len == res.length + @sizeOf(Self)) // res // else // Error.InvalidMessage, // .file_transfer => Error.NotImplementedSaprusType, // else => Error.UnknownSaprusType, // }; // } // ZERO COPY STUFF // &payload could be a void value that is treated as a pointer to a [*]u8 /// All Saprus messages pub const Message = packed struct { const Relay = packed struct { dest: @Vector(4, u8), payload: void, pub fn getPayload(self: *align(1) Relay) []u8 { const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16)); return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @sizeOf(Relay)]; } }; const Connection = packed struct { src_port: u16, // random number > 1024 dest_port: u16, // random number > 1024 seq_num: u32 = 0, msg_id: u32 = 0, reserved: u8 = 0, options: ConnectionOptions = .{}, payload: void, pub fn getPayload(self: *align(1) Connection) []u8 { const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16)); return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @sizeOf(Connection)]; } fn nativeFromNetworkEndian(self: *align(1) Connection) void { self.src_port = bigToNative(@TypeOf(self.src_port), self.src_port); self.dest_port = bigToNative(@TypeOf(self.dest_port), self.dest_port); self.seq_num = bigToNative(@TypeOf(self.seq_num), self.seq_num); self.msg_id = bigToNative(@TypeOf(self.msg_id), self.msg_id); } fn networkFromNativeEndian(self: *align(1) Connection) void { self.src_port = nativeToBig(@TypeOf(self.src_port), self.src_port); self.dest_port = nativeToBig(@TypeOf(self.dest_port), self.dest_port); self.seq_num = nativeToBig(@TypeOf(self.seq_num), self.seq_num); self.msg_id = nativeToBig(@TypeOf(self.msg_id), self.msg_id); } }; const Self = @This(); const SelfBytes = []align(@alignOf(Self)) u8; type: PacketType, length: u16, bytes: void = {}, pub fn init(allocator: Allocator, comptime @"type": PacketType, payload_len: u16) !*Self { const header_size = @sizeOf(switch (@"type") { .relay => Relay, .connection => Connection, .file_transfer => return Error.NotImplementedSaprusType, else => return Error.UnknownSaprusType, }); const size = payload_len + @sizeOf(Self) + header_size; const bytes = try allocator.alignedAlloc(u8, @alignOf(Self), size); const res: *Self = @ptrCast(bytes.ptr); res.type = @"type"; res.length = payload_len + header_size; return res; } pub fn deinit(self: *Self, allocator: Allocator) void { allocator.free(self.asBytes()); } fn getRelay(self: *Self) *align(1) Relay { return std.mem.bytesAsValue(Relay, &self.bytes); } fn getConnection(self: *Self) *align(1) Connection { return std.mem.bytesAsValue(Connection, &self.bytes); } pub fn getSaprusTypePayload(self: *Self) Error!(union(PacketType) { relay: *align(1) Relay, file_transfer: void, connection: *align(1) Connection, }) { return switch (self.type) { .relay => .{ .relay = self.getRelay() }, .connection => .{ .connection = self.getConnection() }, .file_transfer => Error.NotImplementedSaprusType, else => Error.UnknownSaprusType, }; } pub fn nativeFromNetworkEndian(self: *Self) Error!void { self.type = @enumFromInt(bigToNative( @typeInfo(@TypeOf(self.type)).@"enum".tag_type, @intFromEnum(self.type), )); self.length = bigToNative(@TypeOf(self.length), self.length); errdefer { // If the payload specific headers fail, revert the top level header values self.type = @enumFromInt(nativeToBig( @typeInfo(@TypeOf(self.type)).@"enum".tag_type, @intFromEnum(self.type), )); self.length = nativeToBig(@TypeOf(self.length), self.length); } switch (try self.getSaprusTypePayload()) { .relay => {}, .connection => |*con| con.*.nativeFromNetworkEndian(), // We know other values are unreachable, // because they would have returned an error from the switch condition. else => unreachable, } } pub fn networkFromNativeEndian(self: *Self) Error!void { try switch (try self.getSaprusTypePayload()) { .relay => {}, .connection => |*con| con.*.networkFromNativeEndian(), .file_transfer => Error.NotImplementedSaprusType, else => Error.UnknownSaprusType, }; self.type = @enumFromInt(nativeToBig( @typeInfo(@TypeOf(self.type)).@"enum".tag_type, @intFromEnum(self.type), )); self.length = nativeToBig(@TypeOf(self.length), self.length); } pub fn bytesAsValue(bytes: SelfBytes) !*Self { const res = std.mem.bytesAsValue(Self, bytes); return switch (res.type) { .relay, .connection => if (bytes.len == res.length + @sizeOf(Self)) res else Error.InvalidMessage, .file_transfer => Error.NotImplementedSaprusType, else => Error.UnknownSaprusType, }; } pub fn asBytes(self: *Self) SelfBytes { const size = @sizeOf(Self) + self.length; return @as([*]align(@alignOf(Self)) u8, @ptrCast(self))[0..size]; } }; test "testing variable length zero copy struct" { const gpa = std.testing.allocator; const payload = "Hello darkness my old friend"; // Create a view of the byte slice as a Message const msg: *Message = try .init(gpa, .relay, payload.len); defer msg.deinit(gpa); { // Set the message values { // These are both set by the init call. // msg.type = .relay; // msg.length = payload_len; } const relay = (try msg.getSaprusTypePayload()).relay; relay.dest = .{ 1, 2, 3, 4 }; @memcpy(relay.getPayload(), payload); } { const bytes = msg.asBytes(); // Print the message as hex using the network byte order try msg.networkFromNativeEndian(); // We know the error from nativeFromNetworkEndian is unreachable because // it would have returned an error from networkFromNativeEndian. defer msg.nativeFromNetworkEndian() catch unreachable; std.debug.print("network bytes: {x}\n", .{bytes}); std.debug.print("bytes len: {d}\n", .{bytes.len}); } if (false) { // Illegal behavior std.debug.print("{any}\n", .{(try msg.getSaprusTypePayload()).connection}); } try std.testing.expectEqualDeep(msg, try Message.bytesAsValue(msg.asBytes())); } const std = @import("std"); const Allocator = std.mem.Allocator; const asBytes = std.mem.asBytes; const nativeToBig = std.mem.nativeToBig; const bigToNative = std.mem.bigToNative; test { std.testing.refAllDeclsRecursive(@This()); }