//! By convention, main.zig is where your main function lives in the case that //! you are building an executable. If you are making a library, the convention //! is to delete this file and start with root.zig instead. const is_debug = builtin.mode == .Debug; const SaprusPacketType = enum(u16) { relay = 0x003C, file_transfer = 0x8888, }; const SaprusMessage = union(SaprusPacketType) { relay: struct { dest: [4]u8, payload: []u8, }, file_transfer: void, // unimplemented const Self = @This(); fn toBytes(s: Self, allocator: Allocator) ![]u8 { var buf = std.ArrayList(u8).init(allocator); const w = buf.writer(); try w.writeInt(u16, @intFromEnum(@as(SaprusPacketType, s)), .big); switch (s) { .relay => |relay| { try w.writeAll(&relay.dest); try w.writeInt(u16, @intCast(relay.payload.len), .big); try w.writeAll(relay.payload); }, .file_transfer => unreachable, } return buf.toOwnedSlice(); } }; pub fn main() !void { const DBA = std.heap.DebugAllocator(.{}); var dba: ?DBA = if (comptime is_debug) DBA.init else null; defer if (dba) |*d| { _ = d.deinit(); }; var allocator = if (dba) |*d| d.allocator() else std.heap.smp_allocator; const msg = SaprusMessage{ .relay = .{ .dest = .{ 255, 255, 255, 255 }, .payload = @ptrCast(@constCast("Hello darkness my old friend")), }, }; const msg_bytes = try msg.toBytes(allocator); defer allocator.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); } const builtin = @import("builtin"); const std = @import("std"); const Allocator = std.mem.Allocator; const network = @import("network");