From cb62ac2f63d91b2473cda081d262884b98573725 Mon Sep 17 00:00:00 2001 From: geemili Date: Sun, 3 Mar 2024 02:21:30 -0700 Subject: [PATCH] start implementing mDNS discovery on client side --- src/LocalUI.zig | 167 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 150 insertions(+), 17 deletions(-) diff --git a/src/LocalUI.zig b/src/LocalUI.zig index 31e7884..8c68826 100644 --- a/src/LocalUI.zig +++ b/src/LocalUI.zig @@ -9,7 +9,7 @@ fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response { errdefer arena.deinit(); var join_multiplayer_text = try Element.Text.create(arena.allocator(), "Join Multiplayer Game"); - var join_multiplayer_link = try Element.Link.create(arena.allocator(), join_multiplayer_text.element(), .{ .handler = join_multiplayer_game }); + var join_multiplayer_link = try Element.Link.create(arena.allocator(), join_multiplayer_text.element(), .{ .handler = JoinMultiplayerGame.INTERFACE }); var host_multiplayer_text = try Element.Text.create(arena.allocator(), "Host Multiplayer Game"); var host_multiplayer_link = try Element.Link.create(arena.allocator(), host_multiplayer_text.element(), .{ .handler = HostMultiplayerGame.INTERFACE }); @@ -24,24 +24,157 @@ fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response { return Response{ .arena = arena, .body = .{ .element = page.element() } }; } -pub const join_multiplayer_game = &Handler.Interface{ - .create = null, - .handle = &join_multiplayer_game_handler, - .deinit = null, +const JoinMultiplayerGame = struct { + allocator: std.mem.Allocator, + discovery_thread: std.Thread, + + pub const INTERFACE = &Handler.Interface{ + .create = &create, + .handle = &handler, + .deinit = &deinit, + }; + + fn create(allocator: std.mem.Allocator) Handler.Error!?*anyopaque { + const this = try allocator.create(@This()); + errdefer allocator.destroy(this); + + const discovery_thread = std.Thread.spawn(.{}, find_local_games_thread, .{allocator}) catch return error.OutOfMemory; + + this.* = .{ + .allocator = allocator, + .discovery_thread = discovery_thread, + }; + + return this; + } + + fn handler(pointer: ?*anyopaque, request: Request) Handler.Error!Response { + const this: *@This() = @ptrCast(@alignCast(pointer)); + _ = this; + + var arena = std.heap.ArenaAllocator.init(request.allocator); + errdefer arena.deinit(); + + var text = try Element.Text.create(arena.allocator(), try std.fmt.allocPrint(arena.allocator(), "Looking for multiplayer games", .{})); + + var page = try Element.Page.create(request.allocator); + try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element()); + + return Response{ .arena = arena, .body = .{ .element = page.element() } }; + } + + fn deinit(pointer: ?*anyopaque) void { + const this: *@This() = @ptrCast(@alignCast(pointer)); + + this.allocator.destroy(this); + } + + fn find_local_games_thread(allocator: std.mem.Allocator) !void { + const buffer = try allocator.alloc(u8, 2048); + defer allocator.free(buffer); + + const log = std.log.scoped(.mdns); + log.info("mdns discovery service started", .{}); + + // IPv4 + const mdns_ipv4_socket = c.mdns_socket_open_ipv4(null); + defer c.mdns_socket_close(mdns_ipv4_socket); + + // IPv6 + const mdns_ipv6_socket = c.mdns_socket_open_ipv6(null); + defer c.mdns_socket_close(mdns_ipv6_socket); + + const service_name = "rummy._udp.local."; + for (&[_]c_int{ mdns_ipv4_socket, mdns_ipv6_socket }) |socket| { + switch (c.mdns_query_send(socket, c.MDNS_RECORDTYPE_PTR, service_name.ptr, service_name.len, buffer.ptr, buffer.len, 0)) { + 0 => {}, + else => |err_code| log.warn("Failed to send question; error code = {}", .{err_code}), + } + } + + while (true) { + var pollfds = [_]std.os.pollfd{ + .{ .fd = mdns_ipv4_socket, .events = std.os.POLL.IN, .revents = undefined }, + .{ .fd = mdns_ipv6_socket, .events = std.os.POLL.IN, .revents = undefined }, + }; + _ = std.os.poll(&pollfds, 1000) catch break; + for (pollfds) |pollfd| { + if (pollfd.revents & std.os.POLL.IN == 0) continue; + _ = c.mdns_query_recv(pollfd.fd, buffer.ptr, buffer.len, &find_local_games_thread_query_callback, null, 0); + } + } + + log.info("mdns service stopping", .{}); + } + + fn find_local_games_thread_query_callback( + sock: c_int, + from: ?*const c.sockaddr, + addrlen: usize, + entry: c.mdns_entry_type_t, + query_id: u16, + rtype: u16, + rclass: u16, + ttl: u32, + data_ptr_opaque: ?*const anyopaque, + data_len: usize, + name_offset: usize, + name_length: usize, + record_offset: usize, + record_length: usize, + userdata: ?*anyopaque, + ) callconv(.C) c_int { + const log = std.log.scoped(.mdns); + + _ = rclass; + _ = userdata; + _ = ttl; + _ = name_length; + _ = sock; + _ = addrlen; + _ = query_id; + + // const data_ptr: [*]const u8 = @ptrCast(data_ptr_opaque.?); + // const data = data_ptr[0..data_len]; + + var name_buffer: [128]u8 = undefined; + var offset = name_offset; + const name_mdns_str = c.mdns_string_extract(data_ptr_opaque, data_len, &offset, &name_buffer, name_buffer.len); + const name = name_mdns_str.str[0..name_mdns_str.length]; + + const std_from: ?std.net.Address = if (from) |f| std.net.Address{ .any = @bitCast(f.*) } else null; + + const entrytype_str = switch (entry) { + c.MDNS_ENTRYTYPE_QUESTION => "question", + c.MDNS_ENTRYTYPE_ANSWER => "answer", + c.MDNS_ENTRYTYPE_ADDITIONAL => "additional", + c.MDNS_ENTRYTYPE_AUTHORITY => "authority", + else => "???", + }; + const recordtype_str = switch (rtype) { + c.MDNS_RECORDTYPE_PTR => "PTR", + c.MDNS_RECORDTYPE_SRV => "SRV", + c.MDNS_RECORDTYPE_A => "A", + c.MDNS_RECORDTYPE_AAAA => "AAAA", + c.MDNS_RECORDTYPE_TXT => "TXT", + else => "???", + }; + if (rtype == c.MDNS_RECORDTYPE_PTR) { + log.info("{?} : {s} {s} \"{}\"", .{ std_from, entrytype_str, recordtype_str, std.zig.fmtEscapes(name) }); + } + + switch (rtype) { + c.MDNS_RECORDTYPE_PTR => { + var namebuffer: [128]u8 = undefined; + const namestr = c.mdns_record_parse_ptr(data_ptr_opaque, data_len, record_offset, record_length, &namebuffer, namebuffer.len); + log.info("{?} : {s} {s} \"{}\"", .{ std_from, entrytype_str, recordtype_str, std.zig.fmtEscapes(namestr.str[0..namestr.length]) }); + }, + else => {}, + } + return 0; + } }; -fn join_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error!Response { - var arena = std.heap.ArenaAllocator.init(request.allocator); - errdefer arena.deinit(); - - var text = try Element.Text.create(arena.allocator(), "Joining Multiplayer Game"); - - var page = try Element.Page.create(arena.allocator()); - try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element()); - - return Response{ .arena = arena, .body = .{ .element = page.element() } }; -} - const HostMultiplayerGame = struct { allocator: std.mem.Allocator, mdns_addr4_string: []u8,