const is_debug = builtin.mode == .Debug; const base64Enc = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '='); const base64Dec = std.base64.Base64Decoder.init(std.base64.standard_alphabet_chars, '='); const SaprusPacketType = enum(u16) { relay = 0x003C, file_transfer = 0x8888, connection = 0x00E9, _, }; const SaprusConnectionOptions = 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, }; const SaprusError = error{ NotImplementedSaprusType, UnknownSaprusType, }; const SaprusMessage = union(SaprusPacketType) { const Relay = struct { const Header = packed struct { dest: @Vector(4, u8), }; header: Header, payload: []const u8, }; const Connection = struct { const Header = packed struct { src_port: u16, dest_port: u16, seq_num: u32 = 0, msg_id: u32 = 0, reserved: u8 = 0, options: SaprusConnectionOptions = .{}, }; header: Header, payload: []const u8, }; relay: Relay, file_transfer: void, // unimplemented connection: Connection, fn deinit(self: SaprusMessage, allocator: Allocator) void { switch (self) { .relay => |r| allocator.free(r.payload), .connection => |c| allocator.free(c.payload), else => unreachable, } } fn toBytes(self: SaprusMessage, allocator: Allocator) ![]u8 { var buf = std.ArrayList(u8).init(allocator); const w = buf.writer(); try w.writeInt(u16, @intFromEnum(self), .big); switch (self) { .relay => |r| try toBytesAux(Relay.Header, r.header, r.payload, w, allocator), .connection => |c| try toBytesAux(Connection.Header, c.header, c.payload, w, allocator), .file_transfer => return SaprusError.NotImplementedSaprusType, } return buf.toOwnedSlice(); } fn fromBytes(bytes: []const u8, allocator: Allocator) !SaprusMessage { var s = std.io.fixedBufferStream(bytes); const r = s.reader(); switch (@as(SaprusPacketType, @enumFromInt(try r.readInt(u16, .big)))) { .relay => return fromBytesAux(.relay, Relay.Header, r, allocator), .connection => return fromBytesAux(.connection, Connection.Header, r, allocator), .file_transfer => return SaprusError.NotImplementedSaprusType, else => return SaprusError.UnknownSaprusType, } } }; inline fn toBytesAux( Header: type, header: Header, payload: []const u8, w: std.ArrayList(u8).Writer, allocator: Allocator, ) !void { const payload_list = try encodeToList(payload, allocator); defer payload_list.deinit(); try w.writeStructEndian(header, .big); try w.writeInt(u16, @intCast(payload_list.items.len), .big); try w.writeAll(payload_list.items); } inline fn fromBytesAux( packet: SaprusPacketType, Header: type, r: StringReader, allocator: Allocator, ) !SaprusMessage { const header = try r.readStructEndian(Header, .big); const len = try r.readInt(u16, .big); const payload = try decodeFromReader(r, len, allocator); return @unionInit(SaprusMessage, @tagName(packet), .{ .header = header, .payload = payload, }); } pub fn main() !void { var dba: ?DebugAllocator = if (comptime is_debug) DebugAllocator.init else null; defer if (dba) |*d| { _ = d.deinit(); }; var gpa = if (dba) |*d| d.allocator() else std.heap.smp_allocator; const msg = SaprusMessage{ .relay = .{ .header = .{ .dest = .{ 255, 255, 255, 255 } }, .payload = "Hello darkness my old friend", }, }; const msg_bytes = try msg.toBytes(gpa); defer gpa.free(msg_bytes); try network.init(); defer network.deinit(); var sock = try network.Socket.create(.ipv4, .udp); defer sock.close(); try sock.setBroadcast(true); // Bind to 0.0.0.0:0 const bind_addr = network.EndPoint{ .address = network.Address{ .ipv4 = network.Address.IPv4.any }, .port = 0, }; const dest_addr = network.EndPoint{ .address = network.Address{ .ipv4 = network.Address.IPv4.broadcast }, .port = 8888, }; try sock.bind(bind_addr); _ = try sock.sendTo(dest_addr, msg_bytes); } fn encodeToList(buf: []const u8, allocator: Allocator) !std.ArrayList(u8) { var b64_buf = std.ArrayList(u8).init(allocator); const buf_w = b64_buf.writer(); try base64Enc.encodeWriter(buf_w, buf); return b64_buf; } fn decodeFromReader(reader: StringReader, len: u16, allocator: Allocator) ![]const u8 { var b64_buf = std.ArrayList(u8).init(allocator); defer b64_buf.deinit(); try reader.readAllArrayList(&b64_buf, len); const payload = try allocator.alloc(u8, try base64Dec.calcSizeForSlice(b64_buf.items)); try base64Dec.decode(payload, b64_buf.items); return payload; } const builtin = @import("builtin"); const std = @import("std"); const Allocator = std.mem.Allocator; const DebugAllocator = std.heap.DebugAllocator(.{}); const StringReader = std.io.FixedBufferStream([]const u8).Reader; const network = @import("network"); test "Round trip Relay toBytes and fromBytes" { const gpa = std.testing.allocator; const msg = SaprusMessage{ .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 SaprusMessage.fromBytes(to_bytes, gpa); defer from_bytes.deinit(gpa); try std.testing.expectEqualDeep(msg, from_bytes); } test "Round trip Connection toBytes and fromBytes" { const gpa = std.testing.allocator; const msg = SaprusMessage{ .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 SaprusMessage.fromBytes(to_bytes, gpa); defer from_bytes.deinit(gpa); try std.testing.expectEqualDeep(msg, from_bytes); }