Changes
3 changed files (+199/-163)
-
-
@@ -15,169 +15,6 @@ //// SPDX-License-Identifier: Apache-2.0 namespace Plac { namespace V2 { public class Server : Object { /** * IP address of the server. */ public GLib.InetSocketAddress address { get; construct; } /** * TCP port for WebSocket and HTTP connection. */ public uint16 http_port { get; construct; } /** * String uniquely identifies a server among servers in a network. */ public string id { get; construct; } public string version { get; construct; } /** * User-facing display name. */ public string name { get; construct; } public Server(GLib.InetSocketAddress address, string id, string version, string name, uint16 http_port) { Object(address: address, id: id, version: version, name: name, http_port: http_port); } } public class ServerScanner : Object { /** * Found a server. * * ServerScanner emits this signal everytime server returned discovery response: * if a server A returned a response at t1 then returned again at t2, ServerScanner * emits this signal twice (t1 and t2.) */ public signal void found(Server server); /** * Scan has been aborted due to a socket error. */ public signal void scan_failed(GLib.Error error); private GLib.Cancellable? cancellable = null; public int64 read_timeout_us = 1000 * 1000 * 2; public ServerScanner() { Object(); } public void start() { GLib.log("Plac", LEVEL_DEBUG, "Starting server scanner..."); cancellable = new GLib.Cancellable(); new GLib.Thread<void>("plac-server-scanner", () => { try { var socket = new GLib.Socket(IPV4, DATAGRAM, UDP); socket.set_timeout(3); var inet_addr = new GLib.InetAddress.from_bytes(Sood.DISCOVERY_MULTICAST_IPV4_ADDRESS, IPV4); var sock_addr = new GLib.InetSocketAddress(inet_addr, Sood.DISCOVERY_SERVER_UDP_PORT); while (true) { try { socket.send_to(sock_addr, (uint8[]) Sood.DISCOVERY_QUERY_PREBUILT, cancellable); GLib.log("Plac", LEVEL_DEBUG, "Sent discovery query"); } catch (GLib.Error error) { if (error is GLib.IOError.CANCELLED) { return; } GLib.log("Plac", LEVEL_CRITICAL, "Failed to send discovery query: %s", error.message); var e = error.copy(); GLib.Idle.add(() => { scan_failed(e); return false; }); cancellable = null; return; } while (true) { try { GLib.SocketAddress addr; var bytes = socket.receive_bytes_from(out addr, 512, read_timeout_us, cancellable); if (!(addr is GLib.InetSocketAddress)) { GLib.log("Plac", LEVEL_WARNING, "Received discovery response from non-IP socket"); continue; } Sood.DiscoveryResponse resp; var result = Sood.DiscoveryResponse.parse(out resp, (char[]) bytes.get_data()); if (result != Sood.Result.OK) { GLib.log("Plac", LEVEL_WARNING, "Received malformed discovery response: %s", result.to_string()); continue; } // String in Vala is null-terminated, without exception. // Using non null-terminated string or casting uint8[] to string // will result in out of bound reads. I found no function, class, // or language builtins for allocating null-terminated string from // non-null one. Slicing is the closest I got. var server = new Server( (GLib.InetSocketAddress) addr, resp.unique_id.slice(0, (long) resp.unique_id_len), resp.display_version.slice(0, (long) resp.display_version_len), resp.name.slice(0, (long) resp.name_len), resp.http_port ); GLib.Idle.add(() => { found(server); return false; }); } catch (GLib.Error error) { if (error is GLib.IOError.TIMED_OUT) { break; } if (error is GLib.IOError.CANCELLED) { return; } GLib.log("Plac", LEVEL_CRITICAL, "Failed to read discovery response: %s", error.message); cancellable = null; var e = error.copy(); GLib.Idle.add(() => { scan_failed(e); return false; }); return; } } } } catch (GLib.Error error) { GLib.log("Plac", LEVEL_CRITICAL, "Discovery aborted due to an error: %s", error.message); var e = error.copy(); GLib.Idle.add(() => { scan_failed(e); return false; }); } cancellable = null; }); } public void stop() { GLib.log("Plac", LEVEL_DEBUG, "Stopping server scanner..."); cancellable.cancel(); } } } namespace Discovery { public async Plac.Discovery.ScanResult? resolve_async(string server_id, string ip_addr, uint16 http_port) { GLib.SourceFunc callback = resolve_async.callback;
-
-
src/Plac/Server.vala (new)
-
@@ -0,0 +1,47 @@// 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 namespace Plac { namespace V2 { public class Server : Object { /** * IP address of the server. */ public GLib.InetSocketAddress address { get; construct; } /** * TCP port for WebSocket and HTTP connection. */ public uint16 http_port { get; construct; } /** * String uniquely identifies a server among servers in a network. */ public string id { get; construct; } public string version { get; construct; } /** * User-facing display name. */ public string name { get; construct; } public Server(GLib.InetSocketAddress address, string id, string version, string name, uint16 http_port) { Object(address: address, id: id, version: version, name: name, http_port: http_port); } } } }
-
-
-
@@ -0,0 +1,152 @@// 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 namespace Plac { namespace V2 { public class ServerScanner : Object { /** * Found a server. * * ServerScanner emits this signal everytime server returned discovery response: * if a server A returned a response at t1 then returned again at t2, ServerScanner * emits this signal twice (t1 and t2.) */ public signal void found(Server server); /** * Scan has been aborted due to a socket error. */ public signal void scan_failed(GLib.Error error); private GLib.Cancellable? cancellable = null; public int64 read_timeout_us = 1000 * 1000 * 2; public ServerScanner() { Object(); } public void start() { GLib.log("Plac", LEVEL_DEBUG, "Starting server scanner..."); cancellable = new GLib.Cancellable(); new GLib.Thread<void>("plac-server-scanner", () => { try { var socket = new GLib.Socket(IPV4, DATAGRAM, UDP); socket.set_timeout(3); var inet_addr = new GLib.InetAddress.from_bytes(Sood.DISCOVERY_MULTICAST_IPV4_ADDRESS, IPV4); var sock_addr = new GLib.InetSocketAddress(inet_addr, Sood.DISCOVERY_SERVER_UDP_PORT); while (true) { try { socket.send_to(sock_addr, (uint8[]) Sood.DISCOVERY_QUERY_PREBUILT, cancellable); GLib.log("Plac", LEVEL_DEBUG, "Sent discovery query"); } catch (GLib.Error error) { if (error is GLib.IOError.CANCELLED) { return; } GLib.log("Plac", LEVEL_CRITICAL, "Failed to send discovery query: %s", error.message); var e = error.copy(); GLib.Idle.add(() => { scan_failed(e); return false; }); cancellable = null; return; } while (true) { try { GLib.SocketAddress addr; var bytes = socket.receive_bytes_from(out addr, 512, read_timeout_us, cancellable); if (!(addr is GLib.InetSocketAddress)) { GLib.log("Plac", LEVEL_WARNING, "Received discovery response from non-IP socket"); continue; } Sood.DiscoveryResponse resp; var result = Sood.DiscoveryResponse.parse(out resp, (char[]) bytes.get_data()); if (result != Sood.Result.OK) { GLib.log("Plac", LEVEL_WARNING, "Received malformed discovery response: %s", result.to_string()); continue; } // String in Vala is null-terminated, without exception. // Using non null-terminated string or casting uint8[] to string // will result in out of bound reads. I found no function, class, // or language builtins for allocating null-terminated string from // non-null one. Slicing is the closest I got. var server = new Server( (GLib.InetSocketAddress) addr, resp.unique_id.slice(0, (long) resp.unique_id_len), resp.display_version.slice(0, (long) resp.display_version_len), resp.name.slice(0, (long) resp.name_len), resp.http_port ); GLib.Idle.add(() => { found(server); return false; }); } catch (GLib.Error error) { if (error is GLib.IOError.TIMED_OUT) { break; } if (error is GLib.IOError.CANCELLED) { return; } GLib.log("Plac", LEVEL_CRITICAL, "Failed to read discovery response: %s", error.message); cancellable = null; var e = error.copy(); GLib.Idle.add(() => { scan_failed(e); return false; }); return; } } } } catch (GLib.Error error) { GLib.log("Plac", LEVEL_CRITICAL, "Discovery aborted due to an error: %s", error.message); var e = error.copy(); GLib.Idle.add(() => { scan_failed(e); return false; }); } cancellable = null; }); } public void stop() { GLib.log("Plac", LEVEL_DEBUG, "Stopping server scanner..."); cancellable.cancel(); } } } }
-