/// 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 MessageTypeError = error{ NotImplementedSaprusType, UnknownSaprusType, }; pub const MessageParseError = MessageTypeError || error{ InvalidMessage, }; pub const RelayMessage = struct { dest: Dest, payload: []const u8, pub const @"type": PacketType = .relay; pub const Dest = struct { bytes: [4]u8, /// Asserts bytes is less than or equal to 4 bytes pub fn fromBytes(bytes: []const u8) Dest { var buf: [4]u8 = @splat(0); std.debug.assert(bytes.len <= buf.len); @memcpy(buf[0..bytes.len], bytes); return .{ .bytes = buf }; } }; pub fn init(dest: Dest, payload: []const u8) RelayMessage { return .{ .dest = dest, .payload = payload }; } /// Asserts that buf is large enough to fit the relay message. pub fn toBytes(self: RelayMessage, buf: []u8) []u8 { var out: Writer = .fixed(buf); out.writeInt(u16, @intFromEnum(RelayMessage.type), .big) catch unreachable; out.writeInt(u16, @intCast(self.payload.len + self.dest.bytes.len), .big) catch unreachable; out.writeAll(&self.dest.bytes) catch unreachable; out.writeAll(self.payload) catch unreachable; return out.buffered(); } pub fn fromBytes(buf: []const u8) RelayMessage { var in: Reader = .fixed(buf); } test toBytes { var buf: [1024]u8 = undefined; const relay: RelayMessage = .init( .fromBytes(&.{ 172, 18, 1, 30 }), // zig fmt: off &[_]u8{ 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64 }, // zig fmt: on ); // zig fmt: off var expected = [_]u8{ 0x00, 0x3c, 0x00, 0x17, 0xac, 0x12, 0x01, 0x1e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64 }; // zig fmt: on try std.testing.expectEqualSlices(u8, &expected, relay.toBytes(&buf)); } }; // pub const ConnectionMessage = struct { // length: u16, // src_port: u16, // dest_port: u16, // seq_num: u32 = 0, // msg_id: u32 = 0, // // _reserved: u8 = 0, // options: ConnectionOptions = .{}, // payload: []u8, // pub const type: PacketType = .connection; // /// Asserts that buf is large enough to fit the connection message. // fn toBytes(self: ConnectionMessage, buf: []u8) []u8 { // var out: Writer = .fixed(buf); // out.writeInt(u16, ConnectionMessage.type, .big) catch unreachable; // return w.buffered(); // } // }; // // 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 { // pub 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.* - @bitSizeOf(Relay) / 8]; // } // }; // pub 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.* - @bitSizeOf(Connection) / 8]; // } // 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(1) u8; // type: PacketType, // length: u16, // bytes: void = {}, // /// Takes a byte slice, and returns a Message struct backed by the slice. // /// This properly initializes the top level headers within the slice. // /// This is used for creating new messages. For reading messages from the network, // /// see: networkBytesAsValue. // pub fn init(@"type": PacketType, bytes: []u8) *align(1) Self { // std.debug.assert(bytes.len >= @sizeOf(Self)); // const res: *align(1) Self = @ptrCast(bytes.ptr); // res.type = @"type"; // res.length = @intCast(bytes.len - @sizeOf(Self)); // return res; // } // /// Compute the number of bytes required to store a given payload size for a given message type. // pub fn calcSize(comptime @"type": PacketType, payload_len: usize) MessageTypeError!u16 { // const header_size = @bitSizeOf(switch (@"type") { // .relay => Relay, // .connection => Connection, // .file_transfer => return MessageTypeError.NotImplementedSaprusType, // else => return MessageTypeError.UnknownSaprusType, // }) / 8; // return @intCast(payload_len + @sizeOf(Self) + header_size); // } // fn getRelay(self: *align(1) Self) *align(1) Relay { // return std.mem.bytesAsValue(Relay, &self.bytes); // } // fn getConnection(self: *align(1) Self) *align(1) Connection { // return std.mem.bytesAsValue(Connection, &self.bytes); // } // /// Access the message Saprus payload. // pub fn getSaprusTypePayload(self: *align(1) Self) MessageTypeError!(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 => MessageTypeError.NotImplementedSaprusType, // else => MessageTypeError.UnknownSaprusType, // }; // } // /// Convert the message to native endianness from network endianness in-place. // pub fn nativeFromNetworkEndian(self: *align(1) Self) MessageTypeError!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, // } // } // /// Convert the message to network endianness from native endianness in-place. // pub fn networkFromNativeEndian(self: *align(1) Self) MessageTypeError!void { // try switch (try self.getSaprusTypePayload()) { // .relay => {}, // .connection => |*con| con.*.networkFromNativeEndian(), // .file_transfer => MessageTypeError.NotImplementedSaprusType, // else => MessageTypeError.UnknownSaprusType, // }; // self.type = @enumFromInt(nativeToBig( // @typeInfo(@TypeOf(self.type)).@"enum".tag_type, // @intFromEnum(self.type), // )); // self.length = nativeToBig(@TypeOf(self.length), self.length); // } // /// Convert network endian bytes to a native endian value in-place. // pub fn networkBytesAsValue(bytes: SelfBytes) MessageParseError!*align(1) Self { // const res = std.mem.bytesAsValue(Self, bytes); // try res.nativeFromNetworkEndian(); // return .bytesAsValue(bytes); // } // /// Create a structured view of the bytes without initializing the length or type, // /// and without converting the endianness. // pub fn bytesAsValue(bytes: SelfBytes) MessageParseError!*align(1) Self { // const res = std.mem.bytesAsValue(Self, bytes); // return switch (res.type) { // .relay, .connection => if (bytes.len == res.length + @sizeOf(Self)) // res // else // MessageParseError.InvalidMessage, // .file_transfer => MessageParseError.NotImplementedSaprusType, // else => MessageParseError.UnknownSaprusType, // }; // } // /// Deprecated. // /// If I need the bytes, I should just pass around the slice that is backing this to begin with. // pub fn asBytes(self: *align(1) Self) SelfBytes { // const size = @sizeOf(Self) + self.length; // return @as([*]align(1) u8, @ptrCast(self))[0..size]; // } // }; // test "testing variable length zero copy struct" { // { // // Relay test // const payload = "Hello darkness my old friend"; // var msg_bytes: [try Message.calcSize(.relay, payload.len)]u8 align(@alignOf(Message)) = undefined; // // Create a view of the byte slice as a Message // const msg: *align(1) Message = .init(.relay, &msg_bytes); // { // // 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); // } // { // // 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("relay network bytes: {x}\n", .{msg_bytes}); // std.debug.print("bytes len: {d}\n", .{msg_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())); // } // { // // Connection test // const payload = "Hello darkness my old friend"; // var msg_bytes: [try Message.calcSize(.connection, payload.len)]u8 align(@alignOf(Message)) = undefined; // // Create a view of the byte slice as a Message // const msg: *align(1) Message = .init(.connection, &msg_bytes); // { // // Initializing connection header values // const connection = (try msg.getSaprusTypePayload()).connection; // connection.src_port = 1; // connection.dest_port = 2; // connection.seq_num = 3; // connection.msg_id = 4; // connection.reserved = 5; // connection.options = @bitCast(@as(u8, 6)); // @memcpy(connection.getPayload(), payload); // } // { // // 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("connection network bytes: {x}\n", .{msg_bytes}); // std.debug.print("bytes len: {d}\n", .{msg_bytes.len}); // } // } // } const std = @import("std"); const Allocator = std.mem.Allocator; const Writer = std.Io.Writer; // const asBytes = std.mem.asBytes; // const nativeToBig = std.mem.nativeToBig; // const bigToNative = std.mem.bigToNative; // test "Round trip Relay toBytes and fromBytes" { // if (true) return error.SkipZigTest; // const gpa = std.testing.allocator; // const msg = Message{ // .relay = .{ // .header = .{ .dest = .{ 255, 255, 255, 255 } }, // .payload = "Hello darkness my old friend", // }, // }; // const to_bytes = try msg.toBytes(gpa); // defer gpa.free(to_bytes); // const from_bytes = try Message.fromBytes(to_bytes, gpa); // defer from_bytes.deinit(gpa); // try std.testing.expectEqualDeep(msg, from_bytes); // } // test "Round trip Connection toBytes and fromBytes" { // if (false) { // const gpa = std.testing.allocator; // const msg = Message{ // .connection = .{ // .header = .{ // .src_port = 0, // .dest_port = 0, // }, // .payload = "Hello darkness my old friend", // }, // }; // const to_bytes = try msg.toBytes(gpa); // defer gpa.free(to_bytes); // const from_bytes = try Message.fromBytes(to_bytes, gpa); // defer from_bytes.deinit(gpa); // try std.testing.expectEqualDeep(msg, from_bytes); // } // return error.SkipZigTest; // } test { std.testing.refAllDeclsRecursive(@This()); }