Changes
5 changed files (+173/-102)
-
-
@@ -30,7 +30,6 @@ #else// Windows and macOS seem to use host byte order. fuck. multicast_addr.sin_addr.s_addr = ntohl(sood_discovery_multicast_ipv4_address_be); #endif printf("ADDR=0x%x:%d\n", multicast_addr.sin_addr.s_addr, sood_discovery_server_udp_port); int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) {
-
@@ -83,39 +82,22 @@ close(sockfd);return 1; } sood_message msg; sood_parse_result result = sood_parse(&msg, received, received_size); if (result != SOOD_PARSE_OK) { sood_discovery_response resp; sood_discovery_response_parse_result result = sood_discovery_response_parse( &resp, received, received_size ); if (result != SOOD_DISCOVERY_RESPONSE_PARSE_OK) { printf("Parse error: %d\n", result); close(sockfd); return 2; } sood_message_iter iter; sood_message_iterator(&iter, &msg); sood_message_property property; char key[UINT8_MAX]; char value[UINT16_MAX]; while (1) { sood_message_read_property_result result = sood_message_iter_next(&iter, &property); switch (result) { case SOOD_READ_PROPERTY_DONE: close(sockfd); return 0; case SOOD_READ_PROPERTY_OK: memset(key, 0, sizeof(key)); memset(value, 0, sizeof(value)); memcpy(key, property.key_ptr, property.key_len); memcpy(value, property.value_ptr, property.value_len); printf("%s=%s\n", key, value); break; default: printf("ERR: %d\n", result); close(sockfd); return 1; } } printf("Name: \t\t%.*s\n", resp.name_len, resp.name_ptr); printf("Version: \t%.*s\n", resp.display_version_len, resp.display_version_ptr); printf("Unique ID: \t%.*s\n", resp.unique_id_len, resp.unique_id_ptr); printf("HTTP port: \t%d\n", resp.http_port); return 0; } }
-
-
-
@@ -53,18 +53,16 @@// 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); const response = try sood.discovery.Response.parse(received[0..received_size]); 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(), response.name, response.display_version, response.unique_id, }); std.debug.print("HTTP port: \t{d}\n", .{try response.getHttpPort()}); std.debug.print("HTTP port: \t{d}\n", .{response.http_port}); return; } else |err| switch (err) { std.posix.RecvFromError.WouldBlock => {
-
-
-
@@ -54,6 +54,9 @@ * This function ONLY parses header part: remaining bytes (properties) are unchecked.* Validations are performed on "sood_message_iter_next". Getting "SOOD_PARSE_OK" * does not mean the message is valid. * * If you just want to perform service discovery, use "sood_discovery_response_parse" instead. * This function is for advanced use-cases. * * @param dst - Pointer for message struct to set parsed data. * @param ptr - Pointer for the source bytes. * @param len - Length of the source bytes.
-
@@ -129,6 +132,47 @@ /*** Destination UDP port for IP multicast and broadcast. */ extern const uint16_t sood_discovery_server_udp_port; typedef enum { SOOD_DISCOVERY_RESPONSE_PARSE_OK = 0, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_HEADER_SIZE_MISMATCH = 1, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_INVALID_SIGNATURE = 2, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_EMPTY_KEY = 3, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_KEY_SIZE_MISMATCH = 4, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_NON_UTF8_KEY = 5, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_VALUE_SIZE_CORRUPTED = 6, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_VALUE_SIZE_MISMATCH = 7, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_NON_UTF8_VALUE = 8, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_UNEXPECTED_KIND = 9, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_MISSING_PROPERTY = 10, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_UNEXPECTED_VALUE = 11, } sood_discovery_response_parse_result; typedef struct { uint16_t http_port; const char *name_ptr; size_t name_len; const char *display_version_ptr; size_t display_version_len; const char *unique_id_ptr; size_t unique_id_len; } sood_discovery_response; /** * Parse bytes as SOOD response message. * * This function completely scans properties field and returns SOOD_DISCOVERY_RESPONSE_PARSE_OK * only when the whole message is valid and every required field exists. * * @param dst - Pointer for response struct to set parsed properties. * @param message_ptr - Pointer for source bytes. * @param message_len - Byte size of the source bytes. */ sood_discovery_response_parse_result sood_discovery_response_parse( sood_discovery_response *dst, const char *message_ptr, size_t message_len ); #ifdef __cplusplus }
-
-
-
@@ -130,3 +130,60 @@ 61,sood_discovery_query_prebuilt.len, ); } const sood_discovery_response_parse_result = enum(c_int) { SOOD_DISCOVERY_RESPONSE_PARSE_OK = 0, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_HEADER_SIZE_MISMATCH = 1, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_INVALID_SIGNATURE = 2, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_EMPTY_KEY = 3, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_KEY_SIZE_MISMATCH = 4, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_NON_UTF8_KEY = 5, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_VALUE_SIZE_CORRUPTED = 6, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_VALUE_SIZE_MISMATCH = 7, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_NON_UTF8_VALUE = 8, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_UNEXPECTED_KIND = 9, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_MISSING_PROPERTY = 10, SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_UNEXPECTED_VALUE = 11, }; const sood_discovery_response = extern struct { http_port: u16, name_ptr: [*]const u8, name_len: usize, display_version_ptr: [*]const u8, display_version_len: usize, unique_id_ptr: [*]const u8, unique_id_len: usize, }; export fn sood_discovery_response_parse( dst: *sood_discovery_response, message_ptr: [*]const u8, message_len: usize, ) sood_discovery_response_parse_result { const ParseError = sood.discovery.Response.ParseError; const resp = sood.discovery.Response.parse(message_ptr[0..message_len]) catch |err| return switch (err) { ParseError.InvalidHeaderSize => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_HEADER_SIZE_MISMATCH, ParseError.InvalidSignature => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_INVALID_SIGNATURE, ParseError.EmptyKey => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_EMPTY_KEY, ParseError.IncompleteKey => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_KEY_SIZE_MISMATCH, ParseError.NonUTF8Key => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_NON_UTF8_KEY, ParseError.InvalidValueSizeField => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_VALUE_SIZE_CORRUPTED, ParseError.IncompleteValue => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_VALUE_SIZE_MISMATCH, ParseError.NonUTF8Value => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_NON_UTF8_VALUE, ParseError.NonResponseKindMessage => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_UNEXPECTED_KIND, ParseError.MissingRequiredProperty => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_MISSING_PROPERTY, ParseError.UnexpectedPropertyValue => .SOOD_DISCOVERY_RESPONSE_PARSE_ERROR_UNEXPECTED_VALUE, }; dst.http_port = resp.http_port; dst.name_ptr = resp.name.ptr; dst.name_len = resp.name.len; dst.display_version_ptr = resp.display_version.ptr; dst.display_version_len = resp.display_version.len; dst.unique_id_ptr = resp.unique_id.ptr; dst.unique_id_len = resp.unique_id.len; return .SOOD_DISCOVERY_RESPONSE_PARSE_OK; }
-
-
-
@@ -51,90 +51,80 @@try std.testing.expectEqualSlices(u8, built, Query.prebuilt); } /// Only subset of fields are stored; everything else is omitted. /// To access undocumented fields, use `Message.parse` API instead. pub const Response = struct { message: Message, /// TCP port to use for WebSocket. http_port: u16, pub const InitError = error{ /// Message's type (kind) is not "response" (`R`) NonResponseKindMessage, }; /// Display name of the Roon server. /// /// This string is what user see in Settings/General page and can be changed at Settings/Setup /// page. name: []const u8, pub fn init(message: Message) InitError!@This() { if (message.header.kind != .response) { return InitError.NonResponseKindMessage; } /// Roon server version strings. /// /// 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. display_version: []const u8, return .{ .message = message }; } /// ID unique to the Roon server. unique_id: []const u8, pub const GetPropertyError = error{ pub const SchemaError = error{ /// Message's type (kind) is not "response" (`R`) NonResponseKindMessage, /// 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(); pub const ParseError = SchemaError || Message.Header.ParseError || Message.Properties.ParseError; 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, } } pub fn parse(bytes: []const u8) ParseError!@This() { const msg = try Message.parse(bytes); if (msg.header.kind != .response) { return SchemaError.NonResponseKindMessage; } 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; } var http_port: ?u16 = null; var name: ?[]const u8 = null; var display_version: ?[]const u8 = null; var unique_id: ?[]const u8 = null; /// 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"); } var iter = msg.body.iterator(); /// Returns the Roon server's unique ID string. pub fn getUniqueId(self: @This()) GetPropertyError![]const u8 { return self.getRequiredProperty([]const u8, "unique_id"); } while (try iter.next()) |property| { if (std.mem.eql(u8, "http_port", property.key)) { http_port = std.fmt.parseInt(u16, property.value, 10) catch return SchemaError.UnexpectedPropertyValue; continue; } pub fn getServiceId(self: @This()) GetPropertyError!?[]const u8 { return self.getProperty([]const u8, "service_id"); } if (std.mem.eql(u8, "name", property.key)) { name = property.value; continue; } pub fn getTcpPort(self: @This()) GetPropertyError!?u16 { return self.getProperty(u16, "tcp_port"); } if (std.mem.eql(u8, "display_version", property.key)) { display_version = property.value; continue; } /// Returns TCP port number for WebSocket communication. pub fn getHttpPort(self: @This()) GetPropertyError!u16 { return self.getRequiredProperty(u16, "http_port"); } if (std.mem.eql(u8, "unique_id", property.key)) { unique_id = property.value; continue; } } pub fn getHttpsPort(self: @This()) GetPropertyError!?u16 { return self.getProperty(u16, "https_port"); return Response{ .http_port = http_port orelse return SchemaError.MissingRequiredProperty, .name = name orelse return SchemaError.MissingRequiredProperty, .display_version = display_version orelse return SchemaError.MissingRequiredProperty, .unique_id = unique_id orelse return SchemaError.MissingRequiredProperty, }; } };
-