const base64Enc = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '='); const base64Dec = std.base64.Base64Decoder.init(std.base64.standard_alphabet_chars, '='); const native_endian = @import("builtin").cpu.arch.endian(); rand: Random, writer: *std.Io.Writer, const Self = @This(); const max_message_size = 2048; pub fn init(writer: *std.Io.Writer) !Self { var prng = Random.DefaultPrng.init(blk: { var seed: u64 = undefined; try posix.getrandom(mem.asBytes(&seed)); break :blk seed; }); const rand = prng.random(); return .{ .rand = rand, .writer = writer, }; } pub fn deinit(self: *Self) void { self.writer.flush() catch {}; } /// Used for relay messages and connection handshake. /// Assumes Client .init has been called. fn broadcastInitialInterestMessage(self: *Self, msg_bytes: []u8) !void { const writer = self.writer; const total_len = ((@bitSizeOf(EthernetHeaders) + @bitSizeOf(IpHeaders) + @bitSizeOf(UdpHeaders)) / 8) + msg_bytes.len; std.debug.assert(writer.buffer.len >= total_len); _ = writer.consumeAll(); const ether_headers: EthernetHeaders = .{ .dest_mac = .{ 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff }, .src_mac = .{ 0xee, 0xee, 0xee, 0xee, 0xee, 0xee }, // .src_mac = blk: { // var output_bytes: [6]u8 = undefined; // // const r_bytes = try writer.writableArray(6); // self.rand.bytes(&output_bytes); // break :blk output_bytes; // }, .ether_type = 0x0800, }; // @compileLog((@bitSizeOf(EthernetHeaders) / 8)); const ip_headers: IpHeaders = .{ // .ip_version = 0x4, // .header_length = 0x5, .total_length = @intCast(total_len - 92), .ttl = 0x64, .protocol = 0x11, .src_ip = .{ 0xff, 0x02, 0x03, 0x04 }, .dest_ip = .{ 0xff, 0xff, 0xff, 0xff }, }; const udp_headers: UdpHeaders = .{ .src_port = 0xbbbb, .dest_port = 8888, .length = @intCast(msg_bytes.len), }; try ether_headers.write(writer); try ip_headers.write(writer); try udp_headers.write(writer); // Saprus const msg_target_bytes = try writer.writableSlice(msg_bytes.len); @memcpy(msg_target_bytes, msg_bytes); var msg_target: *align(1) SaprusMessage = try .bytesAsValue(msg_target_bytes); try msg_target.networkFromNativeEndian(); std.debug.print("bytes: {x}\n", .{writer.buffer[0..writer.end]}); try writer.flush(); } // fn broadcastSaprusMessage(msg_bytes: []align(@alignOf(SaprusMessage)) u8) !void {} fn broadcastSaprusMessage(msg_bytes: []u8, udp_port: u16) !void { const msg: *align(1) SaprusMessage = try .bytesAsValue(msg_bytes); try msg.networkFromNativeEndian(); defer msg.nativeFromNetworkEndian() catch unreachable; 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 = udp_port, }; try sock.bind(bind_addr); std.debug.print("{x}\n", .{msg_bytes}); _ = try sock.sendTo(dest_addr, msg_bytes); } pub fn sendRelay(self: *Self, payload: []const u8, dest: [4]u8) !void { var buf: [max_message_size]u8 align(@alignOf(SaprusMessage)) = undefined; const msg_bytes = buf[0..try SaprusMessage.calcSize( .relay, base64Enc.calcSize(payload.len), )]; const msg: *align(1) SaprusMessage = .init(.relay, msg_bytes); const relay = (try msg.getSaprusTypePayload()).relay; relay.dest = dest; _ = base64Enc.encode(relay.getPayload(), payload); try self.broadcastInitialInterestMessage(msg_bytes); } fn randomPort(self: Self) u16 { return self.rand.intRangeAtMost(u16, 1024, 65000); } pub fn sendInitialConnection( self: Self, payload: []const u8, output_bytes: []u8, initial_port: u16, ) !*align(1) SaprusMessage { const dest_port = self.randomPort(); const msg_bytes = output_bytes[0..try SaprusMessage.calcSize( .connection, base64Enc.calcSize(payload.len), )]; const msg: *align(1) SaprusMessage = .init(.connection, msg_bytes); const connection = (try msg.getSaprusTypePayload()).connection; connection.src_port = initial_port; connection.dest_port = dest_port; _ = base64Enc.encode(connection.getPayload(), payload); try broadcastSaprusMessage(msg_bytes, 8888); return msg; } pub fn connect(self: Self, payload: []const u8) !?SaprusConnection { const initial_port = self.randomPort(); var initial_conn_res: ?*align(1) SaprusMessage = null; var sock = try network.Socket.create(.ipv4, .udp); defer sock.close(); // Bind to 255.255.255.255:8888 const bind_addr = network.EndPoint{ .address = network.Address{ .ipv4 = network.Address.IPv4.broadcast }, .port = 8888, }; // timeout 1s try sock.setReadTimeout(1 * std.time.us_per_s); try sock.bind(bind_addr); var sent_msg_bytes: [max_message_size]u8 align(@alignOf(SaprusMessage)) = undefined; const msg = try self.sendInitialConnection(payload, &sent_msg_bytes, initial_port); var response_buf: [max_message_size]u8 align(@alignOf(SaprusMessage)) = undefined; _ = try sock.receive(&response_buf); // Ignore message that I sent. const len = try sock.receive(&response_buf); initial_conn_res = try .networkBytesAsValue(response_buf[0..len]); // Complete handshake after awaiting response try broadcastSaprusMessage(msg.asBytes(), self.randomPort()); if (false) { return initial_conn_res.?; } return null; } const EthernetHeaders = struct { dest_mac: @Vector(6, u8), src_mac: @Vector(6, u8), ether_type: u16, fn write(hdr: @This(), writer: *std.Io.Writer) !void { try writer.writeInt(u48, @bitCast(hdr.dest_mac), .big); try writer.writeInt(u48, @bitCast(hdr.src_mac), .big); try writer.writeInt(u16, hdr.ether_type, .big); } }; const IpHeaders = struct { _: u8 = 0x45, // ip_version: u4, // header_length: u4 = 0, type_of_service: u8 = 0, total_length: u16 = 0x04, identification: u16 = 0, __: u16 = 0x0, // ethernet_flags: u3 = 0, // fragment_offset: u13 = 0, ttl: u8 = 0, protocol: u8 = 0, header_checksum: @Vector(2, u8) = .{ 0, 0 }, src_ip: @Vector(4, u8), dest_ip: @Vector(4, u8), fn write(hdr: @This(), writer: *std.Io.Writer) !void { try writer.writeInt(u8, 0x45, .big); // ip version and header length try writer.writeByte(hdr.type_of_service); try writer.writeInt(u16, hdr.total_length, .big); try writer.writeInt(u16, hdr.identification, .big); try writer.writeInt(u16, 0x00, .big); // ethernet flags and fragment offset try writer.writeByte(hdr.ttl); try writer.writeByte(hdr.protocol); try writer.writeInt(u16, @bitCast(hdr.header_checksum), .big); try writer.writeInt(u32, @bitCast(hdr.src_ip), .big); try writer.writeInt(u32, @bitCast(hdr.dest_ip), .big); } }; const UdpHeaders = packed struct { src_port: u16, dest_port: u16, length: u16, checksum: @Vector(2, u8) = .{ 0, 0 }, fn write(hdr: @This(), writer: *std.Io.Writer) !void { try writer.writeInt(u16, hdr.src_port, .big); try writer.writeInt(u16, hdr.dest_port, .big); try writer.writeInt(u16, hdr.length, .big); try writer.writeInt(u16, @bitCast(hdr.checksum), .big); } }; const SaprusMessage = @import("message.zig").Message; const SaprusConnection = @import("Connection.zig"); const std = @import("std"); const Random = std.Random; const posix = std.posix; const mem = std.mem; const network = @import("network");