start implementing mDNS discovery on client side

dev
LeRoyce Pearson 2024-03-03 02:21:30 -07:00
parent a6fe88b65e
commit cb62ac2f63
1 changed files with 150 additions and 17 deletions

View File

@ -9,7 +9,7 @@ fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
errdefer arena.deinit(); errdefer arena.deinit();
var join_multiplayer_text = try Element.Text.create(arena.allocator(), "Join Multiplayer Game"); 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_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 }); var host_multiplayer_link = try Element.Link.create(arena.allocator(), host_multiplayer_text.element(), .{ .handler = HostMultiplayerGame.INTERFACE });
@ -24,23 +24,156 @@ fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
return Response{ .arena = arena, .body = .{ .element = page.element() } }; return Response{ .arena = arena, .body = .{ .element = page.element() } };
} }
pub const join_multiplayer_game = &Handler.Interface{ const JoinMultiplayerGame = struct {
.create = null, allocator: std.mem.Allocator,
.handle = &join_multiplayer_game_handler, discovery_thread: std.Thread,
.deinit = null,
}; 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;
fn join_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
var arena = std.heap.ArenaAllocator.init(request.allocator); var arena = std.heap.ArenaAllocator.init(request.allocator);
errdefer arena.deinit(); errdefer arena.deinit();
var text = try Element.Text.create(arena.allocator(), "Joining Multiplayer Game"); var text = try Element.Text.create(arena.allocator(), try std.fmt.allocPrint(arena.allocator(), "Looking for multiplayer games", .{}));
var page = try Element.Page.create(arena.allocator()); var page = try Element.Page.create(request.allocator);
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element()); try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
return Response{ .arena = arena, .body = .{ .element = page.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;
}
};
const HostMultiplayerGame = struct { const HostMultiplayerGame = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,