-
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
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
// 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 module contains constants and concret type on top of Message struct.
const std = @import("std");
const Message = @import("Message.zig");
/// Target UDP port to use for sending IP multicast and broadcast.
/// Though you can also listen for this UDP port to receive broadcast from another client,
/// this library does not provide API for handling that case.
pub const udp_port = 9003;
/// IPv4 address for IP multicast. Send SOOD message to this IP address on `udp_port`.
pub const multicast_ipv4_address = [4]u8{ 239, 255, 90, 90 };
pub const Query = struct {
pub const header = Message.Header{ .kind = .query };
/// A required property for a discovery query. Node.js API uses hard-coded ID
/// thus this library uses the same one.
pub const query_service_id = Message.Property{
.key = "query_service_id",
.value = "00720724-5143-4a9b-abac-0e50cba674bb",
};
pub const properties = [_]Message.Property{
query_service_id,
};
/// Premade bytes for minimum discovery query message.
pub const prebuilt = "SOOD\x02Q\x10query_service_id\x00\x2400720724-5143-4a9b-abac-0e50cba674bb";
};
test "Query.prebuilt should equal to one built by `write`" {
const built = try Message.write(
std.testing.allocator,
Query.header,
&Query.properties,
);
defer std.testing.allocator.free(built);
try std.testing.expectEqualSlices(u8, built, Query.prebuilt);
}
pub const Response = struct {
message: Message,
pub const InitError = error{
/// Message's type (kind) is not "response" (`R`)
NonResponseKindMessage,
};
pub fn init(message: Message) InitError!@This() {
if (message.header.kind != .response) {
return InitError.NonResponseKindMessage;
}
return .{ .message = message };
}
pub const GetPropertyError = error{
/// Response message does not contain required property.
MissingRequiredProperty,
/// Cannot convert property value to desired type.
UnexpectedPropertyValue,
} || Message.Properties.ParseError;
/// Iterates over properties and returns a property of the same `key`.
/// Returns `null` if no properties matched to the `key` .
///
/// This function converts the found value into `T`. It currently supports:
/// * `[]const u8` ... returns as-is.
/// * Integers (e.g. `u16`) ... returns the result of `std.fmt.parseInt`.
/// Returns an error if that conversion failed.
inline fn getProperty(self: @This(), comptime T: type, key: []const u8) GetPropertyError!?T {
var iter = self.message.body.iterator();
while (try iter.next()) |prop| {
if (std.mem.eql(u8, key, prop.key)) {
switch (T) {
[]const u8 => return prop.value,
else => return std.fmt.parseInt(T, prop.value, 10) catch GetPropertyError.UnexpectedPropertyValue,
}
}
}
return null;
}
/// Same as `getProperty`, except returns an error in place of `null`.
inline fn getRequiredProperty(self: @This(), comptime T: type, key: []const u8) GetPropertyError!T {
return try self.getProperty(T, key) orelse GetPropertyError.MissingRequiredProperty;
}
/// Returns "Roon Server name" (`name` property).
/// This string is what user see in Settings/General page and can be changed at Settings/Setup
/// page.
pub fn getName(self: @This()) GetPropertyError!?[]const u8 {
return self.getProperty([]const u8, "name");
}
/// Returns Roon server version strings (`display_version` property).
/// As the name suggests, this value is for display purpose. Format is not defined and there is
/// no stability guarantee, therefore parsing of this value is not recommended.
pub fn getDisplayVersion(self: @This()) GetPropertyError!?[]const u8 {
return self.getProperty([]const u8, "display_version");
}
/// Returns the Roon server's unique ID string.
pub fn getUniqueId(self: @This()) GetPropertyError![]const u8 {
return self.getRequiredProperty([]const u8, "unique_id");
}
pub fn getServiceId(self: @This()) GetPropertyError!?[]const u8 {
return self.getProperty([]const u8, "service_id");
}
pub fn getTcpPort(self: @This()) GetPropertyError!?u16 {
return self.getProperty(u16, "tcp_port");
}
/// Returns TCP port number for WebSocket communication.
pub fn getHttpPort(self: @This()) GetPropertyError!u16 {
return self.getRequiredProperty(u16, "http_port");
}
pub fn getHttpsPort(self: @This()) GetPropertyError!?u16 {
return self.getProperty(u16, "https_port");
}
};