Changes
6 changed files (+0/-873)
-
core/src/application.zig (deleted)
-
@@ -1,178 +0,0 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const builtin = @import("builtin"); const std = @import("std"); const moo = @import("moo"); const websocket = @import("websocket"); const Connection = @import("./roon/connection.zig").Connection; const Server = @import("./server/Server.zig").Server; const Extension = @import("./roon/extension.zig").Extension; const PingService = @import("./roon/services/ping.zig").PingService; const RegistryService = @import("./roon/services/registry.zig").RegistryService; const TransportService = @import("./roon/services/transport.zig").TransportService; const allocator = std.heap.c_allocator; const AppExtension = Extension(.{ .id = "jp.pocka.plac", .display_name = "Plac", .version = "0.0.0-dev", .publisher = "Shota FUJI", .email = "pockawoooh@gmail.com", .required_services = &.{TransportService}, .optional_services = &.{}, .provided_services = &.{PingService}, }); pub const Application = extern struct { addr: std.net.Address, conn: ?*Connection = null, /// Returns `null` on OOM. pub fn makeFromServer(server_ptr: ?*const Server) callconv(.C) ?*Application { const server = server_ptr orelse return null; const conn = allocator.create(Application) catch return null; conn.* = .{ .addr = std.net.Address.initPosix(&server.sockaddr), }; return conn; } pub fn free(self_ptr: ?*Application) callconv(.C) void { if (self_ptr) |self| { if (self.conn) |conn| { conn.deinit(); allocator.destroy(conn); } allocator.destroy(self); } } pub const ConnectResultCode = enum(c_int) { ok = 0, unknown_error = 1, null_self_pointer = 2, out_of_memory = 3, registry_down = 4, failed_to_register = 5, websocket_connection_error = 6, listen_thread_spawn_error = 7, }; pub const ConnectResult = extern struct { code: ConnectResultCode, token: ?[*:0]const u8 = null, token_len: usize = 0, pub fn free(self_ptr: ?*ConnectResult) callconv(.C) void { const self = self_ptr orelse return; if (self.token) |token| { allocator.free(token[0..self.token_len]); } allocator.destroy(self); } }; /// Returns `null` when unable to allocate result struct. pub fn connect( self_ptr: ?*Application, saved_token: ?[*:0]const u8, ) callconv(.C) ?*ConnectResult { const result = allocator.create(ConnectResult) catch return null; const self = self_ptr orelse { result.* = .{ .code = .null_self_pointer, }; return result; }; self.connectInternal(saved_token, result) catch |err| { result.* = .{ .code = switch (err) { error.OutOfMemory => .out_of_memory, Connection.InitError.WebSocketClientCreationError, Connection.InitError.WebSocketHandshakeError => .websocket_connection_error, ConnectError.RegistryInfoError => .registry_down, ConnectError.RegistryRegisterError => .failed_to_register, ConnectError.ListeningThreadSpawnError => .listen_thread_spawn_error, else => .unknown_error, }, }; }; return result; } const ConnectError = error{ RegistryInfoError, RegistryRegisterError, ListeningThreadSpawnError, }; fn connectInternal( self: *Application, saved_token: ?[*:0]const u8, result: *ConnectResult, ) !void { const conn = try allocator.create(Connection); conn.* = try Connection.init(allocator, self.addr); errdefer conn.deinit(); conn.listen(PingService.handleRequest) catch { return ConnectError.ListeningThreadSpawnError; }; std.log.debug("Querying registry status...", .{}); const info = RegistryService.info(allocator, conn) catch |err| { std.log.err("Failed to get extension registry status: {s}", .{@errorName(err)}); return ConnectError.RegistryInfoError; }; defer info.deinit(); const extension = AppExtension{ .token = if (saved_token) |tok| std.mem.span(tok) else null, }; std.log.debug("Registering extension {s} token...", .{if (saved_token) |_| "with" else "without"}); const register = RegistryService.register(AppExtension, allocator, conn, extension) catch |err| { std.log.err("Failed to register extension: {s}", .{@errorName(err)}); return ConnectError.RegistryRegisterError; }; defer register.deinit(); self.conn = conn; std.log.debug("Extension registered, application is ready", .{}); const token = try allocator.dupeZ(u8, register.value.token); result.* = .{ .code = .ok, .token = token.ptr, .token_len = token.len, }; } };
-
-
-
@@ -27,107 +27,6 @@ #define PLAC_CORE_H#include <sys/socket.h> /* Server */ // TODO: Remove me typedef struct { struct sockaddr sockaddr; socklen_t const addrlen; char const * const id; unsigned int const id_len; char const * const name; unsigned int const name_len; const char * const version; unsigned int const version_len; } plac_server; plac_server *plac_server_dupe(plac_server*); void plac_server_free(plac_server*); /* ScanOptions */ // TODO: Remove me typedef struct { unsigned int count; unsigned int receive_window_ms; } plac_server_scan_options; void plac_server_scan_options_init(plac_server_scan_options*); /* ScanResultCode */ // TODO: Remove me typedef enum { PLAC_SCAN_OK = 0, PLAC_SCAN_UNKNOWN_ERROR = 1, PLAC_SCAN_OUT_OF_MEMORY = 2, PLAC_SCAN_SOCKET_SETUP_ERROR = 3, PLAC_SCAN_UDP_SEND_ERROR = 4, PLAC_SCAN_UDP_RECV_ERROR = 5, PLAC_SCAN_NULL_POINTER_ARGS = 6, PLAC_SCAN_SOCKET_PERMISSION_ERROR = 7, PLAC_SCAN_TOO_MANY_SOCKET_ERROR = 8, PLAC_SCAN_INVALID_RECEIVE_WINDOW = 9, PLAC_SCAN_NETWORK_UNAVAILABLE = 10, } plac_scan_result_code; /* ScanResult */ // TODO: Remove me typedef struct { plac_scan_result_code const code; void * const __servers; unsigned int const __i; } plac_scan_result; plac_server *plac_scan_result_next(plac_scan_result*); void plac_scan_result_reset(plac_scan_result*); void plac_scan_result_free(plac_scan_result*); /* FindResult */ // TODO: Remove me typedef struct { plac_scan_result_code const code; plac_server * const server; } plac_find_result; void plac_find_result_free(plac_find_result*); /* ServerScanner */ // TODO: Remove me typedef struct { const int *__sockfd; } plac_server_scanner; plac_server_scanner *plac_server_scanner_make(); void plac_server_scanner_free(plac_server_scanner*); plac_scan_result *plac_server_scanner_scan(plac_server_scanner*, plac_server_scan_options*); plac_find_result *plac_server_scanner_find(plac_server_scanner*, char * const, unsigned int); /* Application.ConnectResultCode */ // TODO: Remove me typedef enum { PLAC_APPLICATION_CONNECT_OK = 0, PLAC_APPLICATION_CONNECT_UNKNOWN_ERROR = 1, PLAC_APPLICATION_CONNECT_NULL_SELF_POINTER = 2, PLAC_APPLICATION_CONNECT_OUT_OF_MEMORY = 3, PLAC_APPLICATION_CONNECT_REGISTRY_DOWN = 4, PLAC_APPLICATION_CONNECT_FAILED_TO_REGISTER = 5, PLAC_APPLICATION_CONNECT_WEBSOCKET_CONNECTION_ERROR = 6, PLAC_APPLICATION_CONNECT_LISTEN_THREAD_SPAWN_ERROR = 7, } plac_application_connect_result_code; /* Application.ConnectionResult */ // TODO: Remove me typedef struct { plac_application_connect_result_code const code; char const * const token; unsigned int const token_len; } plac_application_connect_result; void plac_application_connect_result_free(plac_application_connect_result*); /* App.Server.Zone.PlaybackState */ typedef enum {
-
@@ -407,16 +306,5 @@ */typedef void (*plac_cb_restore_complete)(void*); void plac_app_on_restore_complete(plac_app*, plac_cb_restore_complete, void*); void plac_app_on_restore_complete_disarm(plac_app*, plac_cb_restore_complete); /* Application */ // TODO: Remove me typedef struct { struct sockaddr __addr; void *__conn; } plac_application; plac_application *plac_application_make_from_server(plac_server*); plac_application_connect_result *plac_application_connect(plac_application*, char*); void plac_application_free(plac_application*); #endif
-
-
-
@@ -16,9 +16,6 @@ // SPDX-License-Identifier: Apache-2.0//! This is an entrypoint of the static library. pub const server = @import("./server.zig"); pub const Application = @import("./application.zig").Application; pub const App = @import("./App.zig"); pub const services = struct {
-
@@ -68,27 +65,4 @@ @export(&App.CApi.onConnectionChange, .{ .name = "plac_app_on_connection_change" });@export(&App.CApi.onConnectionChangeDisarm, .{ .name = "plac_app_on_connection_change_disarm" }); @export(&App.CApi.onRestoreComplete, .{ .name = "plac_app_on_restore_complete" }); @export(&App.CApi.onRestoreCompleteDisarm, .{ .name = "plac_app_on_restore_complete_disarm" }); // -- Old API -- @export(&server.Server.dupe, .{ .name = "plac_server_dupe" }); @export(&server.Server.free, .{ .name = "plac_server_free" }); @export(&server.ScanOptions.init, .{ .name = "plac_server_scan_options_init" }); @export(&server.ScanResult.next, .{ .name = "plac_scan_result_next" }); @export(&server.ScanResult.reset, .{ .name = "plac_scan_result_reset" }); @export(&server.ScanResult.free, .{ .name = "plac_scan_result_free" }); @export(&server.FindResult.free, .{ .name = "plac_find_result_free" }); @export(&server.ServerScanner.make, .{ .name = "plac_server_scanner_make" }); @export(&server.ServerScanner.free, .{ .name = "plac_server_scanner_free" }); @export(&server.ServerScanner.scan, .{ .name = "plac_server_scanner_scan" }); @export(&server.ServerScanner.find, .{ .name = "plac_server_scanner_find" }); @export(&Application.makeFromServer, .{ .name = "plac_application_make_from_server" }); @export(&Application.free, .{ .name = "plac_application_free" }); @export(&Application.connect, .{ .name = "plac_application_connect" }); @export(&Application.ConnectResult.free, .{ .name = "plac_application_connect_result_free" }); }
-
-
core/src/server.zig (deleted)
-
@@ -1,18 +0,0 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 pub usingnamespace @import("./server/Server.zig"); pub usingnamespace @import("./server/scanner.zig");
-
-
core/src/server/Server.zig (deleted)
-
@@ -1,130 +0,0 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const sood = @import("sood"); const allocator = std.heap.c_allocator; pub const Server = extern struct { /// Network address of the server. sockaddr: std.posix.sockaddr, addrlen: std.posix.socklen_t, /// Unique ID of the server. It won't change even on server reboot. id: [*:0]const u8, id_len: usize, /// Display name of the server. Users who can configure the server can change the name. name: [*:0]const u8, name_len: usize, /// Free-format version string. version: [*:0]const u8, version_len: usize, /// Returns `null` on OOM. pub fn dupe(ptr: ?*const Server) callconv(.C) ?*Server { if (ptr) |server| { var new = allocator.create(Server) catch return null; new.id = allocator.dupeZ(u8, server.id[0..server.id_len]) catch return null; new.id_len = server.id_len; new.name = allocator.dupeZ(u8, server.name[0..server.name_len]) catch return null; new.name_len = server.name_len; new.version = allocator.dupeZ(u8, server.version[0..server.version_len]) catch return null; new.version_len = server.version_len; new.sockaddr = server.sockaddr; new.addrlen = server.addrlen; return new; } else { return null; } } pub fn free(ptr: ?*Server) callconv(.C) void { if (ptr) |server| { allocator.free(server.id[0..server.id_len]); allocator.free(server.name[0..server.name_len]); allocator.free(server.version[0..server.version_len]); allocator.destroy(server); } } pub fn fromSoodResponse(addr: std.net.Address, response: *const sood.discovery.Response) std.mem.Allocator.Error!*Server { var ip_addr = addr; ip_addr.setPort(response.http_port); const unique_id = try allocator.dupeZ(u8, response.unique_id); errdefer allocator.free(unique_id); const name = try allocator.dupeZ(u8, response.name); errdefer allocator.free(name); const version = try allocator.dupeZ(u8, response.display_version); errdefer allocator.free(version); const entry = try allocator.create(Server); errdefer allocator.destroy(entry); entry.* = .{ .sockaddr = ip_addr.any, .addrlen = ip_addr.getOsSockLen(), .id = unique_id.ptr, .id_len = unique_id.len, .name = name.ptr, .name_len = name.len, .version = version.ptr, .version_len = version.len, }; return entry; } pub fn getAddr(self: *const Server) std.net.Address { return std.net.Address.initPosix(&self.sockaddr); } pub fn getId(self: *const Server) []const u8 { return self.id[0..self.id_len]; } pub fn getName(self: *const Server) []const u8 { return self.name[0..self.name_len]; } pub fn getVersion(self: *const Server) []const u8 { return self.version[0..self.version_len]; } pub fn jsonStringify(self: *const Server, jws: anytype) !void { try jws.beginObject(); try jws.objectField("unique_id"); try jws.write(self.getId()); try jws.objectField("name"); try jws.write(self.getName()); try jws.objectField("version"); try jws.write(self.getVersion()); try jws.objectField("address"); try jws.print("\"{}\"", .{self.getAddr()}); try jws.endObject(); } };
-
-
core/src/server/scanner.zig (deleted)
-
@@ -1,409 +0,0 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const sood = @import("sood"); const Server = @import("./Server.zig").Server; const allocator = std.heap.c_allocator; pub const ScanOptions = extern struct { /// How many times a scanner sends request UDP message? count: c_uint, /// How long will a scanner wait for response UDP message? receive_window_ms: c_uint, /// Set default option values. pub fn init(ptr: ?*ScanOptions) callconv(.C) void { if (ptr) |opts| { opts.count = 1; opts.receive_window_ms = 1_000; } } }; pub const ScanResultCode = enum(c_int) { ok = 0, unknown_error = 1, out_of_memory = 2, socket_setup_error = 3, udp_send_error = 4, udp_recv_error = 5, null_pointer_args = 6, socket_permission_error = 7, too_many_socket_error = 8, invalid_receive_window = 9, network_unavailable = 10, pub fn fromScanError(err: ServerScanner.ScanError) ScanResultCode { return switch (err) { ServerScanner.ScanError.Unknown => .unknown_error, ServerScanner.ScanError.OutOfMemory => .out_of_memory, ServerScanner.ScanError.SocketSetupError => .socket_setup_error, ServerScanner.ScanError.UDPSendError => .udp_send_error, ServerScanner.ScanError.UDPRecvError => .udp_recv_error, ServerScanner.ScanError.SocketPermissionDenied => .socket_permission_error, ServerScanner.ScanError.TooManySocketError => .too_many_socket_error, ServerScanner.ScanError.InvalidReceiveWindow => .invalid_receive_window, ServerScanner.ScanError.NetworkUnavailable => .network_unavailable, }; } }; pub const ScanResult = extern struct { code: ScanResultCode, servers: ?*const []*Server = null, i: usize = 0, /// Returns `null` when the first argument is `null`, `code` field is /// not "ok", or iterator reaches to the end. /// Caller owns the returned `Server`. /// Call `.free()` after use. pub fn next(self_ptr: ?*ScanResult) callconv(.C) ?*Server { const self = self_ptr orelse return null; const servers = self.servers orelse return null; if (self.i >= servers.len) { return null; } const found = servers.*[self.i]; self.i += 1; return found.dupe(); } /// Reset internal iterator. pub fn reset(self_ptr: ?*ScanResult) callconv(.C) void { if (self_ptr) |self| { self.i = 0; } } /// Call to this function does NOT invalidate "Servers" previously returned by `next()`. pub fn free(self_ptr: ?*ScanResult) callconv(.C) void { if (self_ptr) |self| { if (self.servers) |servers| { for (servers.*) |server| { server.free(); } allocator.free(servers.*); allocator.destroy(servers); } allocator.destroy(self); } } }; pub const FindResult = extern struct { code: ScanResultCode, server: ?*Server = null, pub fn free(self_ptr: ?*FindResult) callconv(.C) void { if (self_ptr) |self| { if (self.server) |server| { server.free(); } allocator.destroy(self); } } }; pub const ServerScanner = extern struct { sockfd: ?*std.posix.socket_t = null, /// Returns `null` on OOM. pub fn make() callconv(.C) ?*ServerScanner { const scanner = allocator.create(ServerScanner) catch return null; scanner.* = .{}; return scanner; } pub fn free(self_ptr: ?*ServerScanner) callconv(.C) void { ServerScanner.close(self_ptr); if (self_ptr) |self| { allocator.destroy(self); } } const udp_dst = std.net.Address.initIp4( sood.discovery.multicast_ipv4_address, sood.discovery.udp_port, ); const ScanError = error{ Unknown, SocketSetupError, UDPSendError, UDPRecvError, SocketPermissionDenied, TooManySocketError, InvalidReceiveWindow, NetworkUnavailable, } || std.mem.Allocator.Error; fn setupSocket(self: *ServerScanner) ScanError!*std.posix.socket_t { if (self.sockfd) |sockfd| { return sockfd; } const fd = try allocator.create(std.posix.socket_t); errdefer allocator.destroy(fd); std.log.debug("Opening UDP socket...", .{}); fd.* = std.posix.socket(std.posix.AF.INET, std.posix.SOCK.DGRAM, 0) catch |err| { return switch (err) { std.posix.SocketError.PermissionDenied => ScanError.SocketPermissionDenied, std.posix.SocketError.SystemFdQuotaExceeded, std.posix.SocketError.ProcessFdQuotaExceeded => ScanError.TooManySocketError, std.posix.SocketError.SystemResources => ScanError.OutOfMemory, else => ScanError.SocketSetupError, }; }; errdefer std.posix.close(fd.*); std.posix.setsockopt( fd.*, std.posix.SOL.SOCKET, std.posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)), ) catch |err| return switch (err) { std.posix.SetSockOptError.PermissionDenied => ScanError.SocketPermissionDenied, std.posix.SetSockOptError.SystemResources => ScanError.OutOfMemory, else => ScanError.SocketSetupError, }; self.sockfd = fd; return fd; } inline fn setSocketReceiveTimeout(sockfd: *std.posix.socket_t, timeout_ms: u32) ScanError!void { const sec = std.math.divFloor(u32, timeout_ms, 1_000) catch { return ScanError.InvalidReceiveWindow; }; const usec = @min(std.math.maxInt(i32), 1_000 * (std.math.rem(u32, timeout_ms, 1_000) catch { return ScanError.InvalidReceiveWindow; })); std.log.debug("Setting UDP read timeout to {d}ms ({d}sec, {d}usec)", .{ timeout_ms, sec, usec, }); const timeout = std.posix.timeval{ .sec = sec, .usec = usec }; std.posix.setsockopt( sockfd.*, std.posix.SOL.SOCKET, std.posix.SO.RCVTIMEO, &std.mem.toBytes(timeout), ) catch |err| return switch (err) { std.posix.SetSockOptError.PermissionDenied => ScanError.SocketPermissionDenied, std.posix.SetSockOptError.SystemResources => ScanError.OutOfMemory, else => ScanError.SocketSetupError, }; } inline fn sendDiscoveryQuery(sockfd: *std.posix.socket_t) ScanError!void { std.log.debug("Sending server discovery message to {}", .{udp_dst}); _ = std.posix.sendto( sockfd.*, sood.discovery.Query.prebuilt, 0, &udp_dst.any, udp_dst.getOsSockLen(), ) catch |err| return switch (err) { std.posix.SendToError.NetworkSubsystemFailed, std.posix.SendToError.NetworkUnreachable => ScanError.NetworkUnavailable, else => ScanError.UDPSendError, }; } /// Returns `null` on OOM or when an argument is a null pointer. pub fn scan( self_ptr: ?*ServerScanner, opts_ptr: ?*const ScanOptions, ) callconv(.C) ?*ScanResult { const self = self_ptr orelse return null; const result = allocator.create(ScanResult) catch return null; const opts = opts_ptr orelse { result.* = .{ .code = .null_pointer_args, }; return result; }; self.scan_internal(opts, result) catch |err| { result.* = .{ .code = ScanResultCode.fromScanError(err), }; }; return result; } fn scan_internal(self: *ServerScanner, opts: *const ScanOptions, result: *ScanResult) ScanError!void { const sockfd = try self.setupSocket(); try setSocketReceiveTimeout(sockfd, opts.receive_window_ms); var servers = std.StringHashMap(*Server).init(allocator); defer servers.deinit(); for (0..opts.count) |_| { try sendDiscoveryQuery(sockfd); while (true) { std.log.debug("Got message on UDP socket", .{}); // Discovery response from servers usually fits under 300 bytes. // Extra bytes for safety. var received: [512]u8 = undefined; var src: std.net.Address = undefined; var src_len: std.posix.socklen_t = udp_dst.getOsSockLen(); const size = std.posix.recvfrom( sockfd.*, &received, 0, &src.any, &src_len, ) catch |err| switch (err) { std.posix.RecvFromError.WouldBlock => break, std.posix.RecvFromError.MessageTooBig => continue, else => return ScanError.UDPRecvError, }; const response = sood.discovery.Response.parse(received[0..size]) catch |err| { std.log.warn( "Unable to parse received UDP message as SOOD message: {s}", .{@errorName(err)}, ); // Non-SOOD message. Unlikely but technically possible. continue; }; const stale = servers.get(response.unique_id); defer if (stale) |server| { server.free(); }; try servers.put(response.unique_id, try Server.fromSoodResponse(src, &response)); } } const servers_slice = try allocator.create([]*Server); errdefer allocator.destroy(servers_slice); servers_slice.* = try allocator.alloc(*Server, servers.count()); errdefer allocator.free(servers_slice.*); var i: usize = 0; var iter = servers.valueIterator(); while (iter.next()) |server| { servers_slice.*[i] = server.*; i += 1; } result.* = .{ .code = .ok, .servers = servers_slice, }; } /// Returns `null` on OOM or when an argument is a null pointer. pub fn find(self_ptr: ?*ServerScanner, unique_id: [*:0]const u8, unique_id_len: usize) callconv(.C) ?*FindResult { const self = self_ptr orelse return null; const unique_id_slice = unique_id[0..unique_id_len]; const result = allocator.create(FindResult) catch return null; self.findInternal(unique_id_slice, result) catch |err| { result.* = .{ .code = ScanResultCode.fromScanError(err), }; }; return result; } fn findInternal(self: *ServerScanner, unique_id: []const u8, result: *FindResult) ScanError!void { const sockfd = try self.setupSocket(); try setSocketReceiveTimeout(sockfd, 1_000); // Roon Server throttles discovery response to 3~5 secs. for (0..10) |_| { try sendDiscoveryQuery(sockfd); while (true) { // Discovery response from servers usually fits under 300 bytes. // Extra bytes for safety. var received: [512]u8 = undefined; var src: std.net.Address = undefined; var src_len: std.posix.socklen_t = udp_dst.getOsSockLen(); const size = std.posix.recvfrom( sockfd.*, &received, 0, &src.any, &src_len, ) catch |err| switch (err) { std.posix.RecvFromError.WouldBlock => break, std.posix.RecvFromError.MessageTooBig => continue, else => return ScanError.UDPRecvError, }; const response = sood.discovery.Response.parse(received[0..size]) catch { // Non-SOOD message. Unlikely but technically possible. continue; }; if (!std.mem.eql(u8, response.unique_id, unique_id)) { continue; } result.* = .{ .code = .ok, .server = try Server.fromSoodResponse(src, &response), }; return; } } result.* = .{ .code = .ok, }; } pub fn close(self_ptr: ?*ServerScanner) callconv(.C) void { if (self_ptr) |self| { if (self.sockfd) |sockfd| { std.posix.close(sockfd.*); allocator.destroy(sockfd); } } } };
-