-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
// Copyright 2025 Shota FUJI
//
// Licensed under the Zero-Clause BSD License or the Apache License, Version 2.0, at your option.
// You may not use, copy, modify, or distribute this file except according to those terms. You can
// find a copy of the Zero-Clause BSD License at LICENSES/0BSD.txt, and a copy of the Apache License,
// Version 2.0 at LICENSES/Apache-2.0.txt. You may also obtain a copy of the Apache License, Version
// 2.0 at <https://www.apache.org/licenses/LICENSE-2.0>
//
// SPDX-License-Identifier: 0BSD OR Apache-2.0
//! This example searches Roon Core on computer's subnet then print found one's information on
//! stderr.
//!
//! Since there is no simple cross-platform way to enumerate network interface in Zig, broadcasting
//! is omitted. If you do the same, test by yourself whether multicast is sufficient.
//!
//! In real world application, you should use non-blocking receive and/or configure timeout using
//! actual measurements and usecases.
const std = @import("std");
const sood = @import("sood");
pub fn main() !void {
// Prepare multicast address and SOOD port to pass them to system.
const send_addr = std.net.Address.initIp4(sood.discovery.multicast_ipv4_address, sood.discovery.udp_port);
// Creates a UDP socket (INET = IPv4, DGRAM = Datagrams)
const sockfd = try std.posix.socket(std.posix.AF.INET, std.posix.SOCK.DGRAM, 0);
defer std.posix.close(sockfd);
// Allow reuse of socket address. Doing the same as what Node.js API does.
try std.posix.setsockopt(sockfd, std.posix.SOL.SOCKET, std.posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));
// Cancel receive call at 5 seconds. Repeat send->recv if timeout happened.
const timeout = std.posix.timeval{ .sec = 5, .usec = 0 };
try std.posix.setsockopt(sockfd, std.posix.SOL.SOCKET, std.posix.SO.RCVTIMEO, &std.mem.toBytes(timeout));
// We're not setting these socket options here:
// * this example only sends multicast, so `SOL_SOCKET, SO_BROADCAST, 1` is not required.
// * Zig seems not to have `IP_MULTICAST_TTL`, so `IPPROTO_IP, IP_MULTICAST_TTL, 1` is not here.
while (true) {
// Sends a prebuilt query bytes to the mutlicast address.
_ = try std.posix.sendto(
sockfd,
sood.discovery.Query.prebuilt,
0,
&send_addr.any,
send_addr.getOsSockLen(),
);
// 512 bytes ... reasonable limit. In my testing, message size was less than 300 bytes.
var received: [512]u8 = undefined;
if (std.posix.recv(sockfd, &received, 0)) |received_size| {
const received_msg = try sood.Message.parse(received[0..received_size]);
// `discovery.Response` struct handles known key retrieval, necessary type casting and
// error-handlings. For known fields, use of this struct is recommended.
const response = try sood.discovery.Response.init(received_msg);
std.debug.print("Name: \t\t{s}\nVersion: \t{s}\nUnique ID: \t{s}\n", .{
try response.getName() orelse "<empty>",
try response.getDisplayVersion() orelse "<empty>",
try response.getUniqueId(),
});
std.debug.print("HTTP port: \t{d}\n", .{try response.getHttpPort()});
return;
} else |err| switch (err) {
std.posix.RecvFromError.WouldBlock => {
std.debug.print("Retrying...\n", .{});
continue;
},
else => return err,
}
}
}