Changes
6 changed files (+400/-66)
-
-
@@ -70,6 +70,7 @@ .target = target,.optimize = optimize, }); example_c.addCSourceFile(.{ .file = b.path("examples/basic.c") }); example_c.linkLibC(); example_c.linkLibrary(lib); example_c.addIncludePath(.{ .cwd_relative = b.h_dir }); example_c_step.dependOn(&b.addInstallArtifact(example_c, .{}).step);
-
-
-
@@ -8,9 +8,114 @@ // 2.0 at <https://www.apache.org/licenses/LICENSE-2.0>// // SPDX-License-Identifier: 0BSD OR Apache-2.0 #include <errno.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <sys/time.h> #include <unistd.h> #include <sood.h> int main(int argc, char **argv) { printf("%d\n", sood_add(1, 2)); struct sockaddr_in multicast_addr; multicast_addr.sin_family = AF_INET; // Port is network byte order both in Linux and macOS multicast_addr.sin_port = htons(sood_discovery_server_udp_port); #ifdef __linux__ // Linux uses network byte order for IP addr part. Great consistency. multicast_addr.sin_addr.s_addr = sood_discovery_multicast_ipv4_address_be; #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) { printf("Failed to create socket: %d\n", errno); return 1; } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) { printf("Failed to set SO_REUSEADDR=1: %d\n", errno); close(sockfd); return 1; } struct timeval timeout; timeout.tv_sec = 5; timeout.tv_usec = 0; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { printf("Failed to set SO_RCVTIMEO: %d\n", errno); close(sockfd); return 1; } while (1) { if ( sendto( sockfd, sood_discovery_query_prebuilt, sizeof(sood_discovery_query_prebuilt), 0, (struct sockaddr*)&multicast_addr, sizeof(multicast_addr) ) < 0 ) { printf("Failed to send: %d\n", errno); close(sockfd); return 1; } char received[512]; ssize_t received_size = recv(sockfd, received, sizeof(received), 0); if (received_size < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { printf("Retrying...\n"); continue; } printf("Failed to recv: %d\n", errno); close(sockfd); return 1; } sood_message msg; sood_parse_result result = sood_parse(&msg, received, received_size); if (result != SOOD_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; } } } }
-
-
-
@@ -87,31 +87,75 @@ key: []const u8,value: []const u8, }; pub const Properties = struct { bytes: []const u8, pub const ReadPropertyError = error{ /// Value of key size field is 0. EmptyKey, pub const ParseError = error{ /// Value of key size field is 0. EmptyKey, /// Key is not long enough indicated by size field. IncompleteKey, /// Key is not long enough indicated by size field. IncompleteKey, /// Key is not valid UTF-8 string. NonUTF8Key, /// Value size field is missing, or lacking a byte. InvalidValueSizeField, /// Key is not valid UTF-8 string. NonUTF8Key, /// Value is not long enough indicated by size field. IncompleteValue, /// Same key appeared more than once. DuplicatedKey, /// Value is not valid UTF-8 string. NonUTF8Value, }; /// Value size field is missing, or lacking a byte. InvalidValueSizeField, pub fn readProperty(bytes: []const u8, dst: *Property) ReadPropertyError!usize { var i: usize = 0; const key_size = bytes[i]; i += 1; if (key_size == 0) { return ReadPropertyError.EmptyKey; } /// Value is not long enough indicated by size field. IncompleteValue, const key_start = i; i += key_size; if (i > bytes.len) { return ReadPropertyError.IncompleteKey; } const key = bytes[key_start..i]; if (!std.unicode.utf8ValidateSlice(key)) { return ReadPropertyError.NonUTF8Key; } /// Value is not valid UTF-8 string. NonUTF8Value, }; const value_size_start = i; i += 2; if (i > bytes.len) { return ReadPropertyError.InvalidValueSizeField; } const value_size = std.mem.readInt( u16, &.{ bytes[value_size_start], bytes[value_size_start + 1] }, .big, ); const value_start = i; i += value_size; if (i > bytes.len) { return ReadPropertyError.IncompleteValue; } const value = bytes[value_start..i]; if (!std.unicode.utf8ValidateSlice(value)) { return ReadPropertyError.NonUTF8Value; } dst.key = key; dst.value = value; return i; } pub const Properties = struct { bytes: []const u8, pub const ParseError = ReadPropertyError; pub const Iterator = struct { /// Position in the `bytes`.
-
@@ -120,7 +164,7 @@/// Reference to the Message. props: *const Properties, pub fn next(it: *Iterator) ParseError!?Property { pub fn next(it: *Iterator) ReadPropertyError!?Property { if (it.i >= it.props.bytes.len) { return null; }
-
@@ -130,44 +174,10 @@ errdefer {it.i = start_i; } const key_size = it.props.bytes[it.i]; it.i += 1; if (key_size == 0) { return ParseError.EmptyKey; } var p: Property = undefined; it.i += try readProperty(it.props.bytes[it.i..], &p); const key_start = it.i; it.i += key_size; if (it.i > it.props.bytes.len) { return ParseError.IncompleteKey; } const key = it.props.bytes[key_start..it.i]; if (!std.unicode.utf8ValidateSlice(key)) { return ParseError.NonUTF8Key; } const value_size_start = it.i; it.i += 2; if (it.i > it.props.bytes.len) { return ParseError.InvalidValueSizeField; } const value_size = std.mem.readInt( u16, &.{ it.props.bytes[value_size_start], it.props.bytes[value_size_start + 1] }, .big, ); const value_start = it.i; it.i += value_size; if (it.i > it.props.bytes.len) { return ParseError.IncompleteValue; } const value = it.props.bytes[value_start..it.i]; if (!std.unicode.utf8ValidateSlice(value)) { return ParseError.NonUTF8Value; } return .{ .key = key, .value = value }; return p; } };
-
-
-
@@ -10,12 +10,15 @@ // SPDX-License-Identifier: 0BSD OR Apache-2.0// // === // // Header file for the C-compatible library file (compile artifact of `c.zig`). This file is hand- // written due to Zig compiler cannot generate header file for now. See more details at // <https://github.com/ziglang/zig/issues/9698>. And worse, as there is no check process/tool for // verifying C header file and ABI, you should carefully review the code here. Once Zig is capable // of automatically generating C header file, let's get rid of this file immediately. // <https://github.com/ziglang/zig/issues/20654> // C99 header file for the C-compatible library binary (compile artifact of `c.zig`). // As there is no check process/tool for verifying C header file and ABI, you should carefully // review the code here. // // Here is the design guideline for this C-API: // * No allocation. (internal Zig code uses stack a lot so it's not that efficient though.) // * No opaque struct. Add fields carefully. // * Don't use macro for enums. You can copy and paste enum body between C<->Zig. // * Keep the usage of preprocessors as low as possible. #ifndef SOOD_H #define SOOD_H
-
@@ -26,7 +29,106 @@ #endif#include <stdint.h> int32_t sood_add(int32_t a, int32_t b); typedef enum { SOOD_MESSAGE_UNKNOWN = 0, SOOD_MESSAGE_QUERY = 1, SOOD_MESSAGE_RESPONSE = 2, } sood_message_kind; typedef struct { const char *raw_ptr; size_t raw_len; sood_message_kind kind; } sood_message; typedef enum { SOOD_PARSE_OK = 0, SOOD_PARSE_ERROR_HEADER_SIZE_MISMATCH = 1, SOOD_PARSE_ERROR_INVALID_SIGNATURE = 2, } sood_parse_result; /** * Parse bytes as SOOD message. * * 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. * * @param dst - Pointer for message struct to set parsed data. * @param ptr - Pointer for the source bytes. * @param len - Length of the source bytes. */ sood_parse_result sood_parse(sood_message *dst, const char *ptr, size_t len); typedef struct { const char *key_ptr; size_t key_len; const char *value_ptr; size_t value_len; } sood_message_property; typedef enum { SOOD_READ_PROPERTY_OK = 0, SOOD_READ_PROPERTY_DONE = 1, SOOD_READ_PROPERTY_ERROR_EMPTY_KEY = 2, SOOD_READ_PROPERTY_ERROR_KEY_SIZE_MISMATCH = 3, SOOD_READ_PROPERTY_ERROR_NON_UTF8_KEY = 4, SOOD_READ_PROPERTY_ERROR_VALUE_SIZE_CORRUPTED = 5, SOOD_READ_PROPERTY_ERROR_VALUE_SIZE_MISMATCH = 6, SOOD_READ_PROPERTY_ERROR_NON_UTF8_VALUE = 7, } sood_message_read_property_result; typedef struct { const sood_message *msg; size_t i; } sood_message_iter; /** * Initializes an iterator for message properties. * * @param dst - Pointer for property struct to initialize. * @param msg - Message to iterate on. */ void sood_message_iterator(sood_message_iter *dst, const sood_message *msg); /** * Moves iterator next and parse next property into "dst". * * If iterator is at the end of body, this function returns "SOOD_READ_PROPERTY_DONE". * This function returns "SOOD_READ_PROPERTY_ERROR_*" when encountered message fields * violating SOOD message properties schema. * * @param iter - Iterator to iterate on. * @param dst - Pointer for property struct to put read key/value in. */ sood_message_read_property_result sood_message_iter_next( sood_message_iter *iter, sood_message_property *dst ); /** * Minimum pre-constructed bytes of SOOD discovery query. * * If you don't use "_tid" field, simply send this bytes over UDP. */ extern const char sood_discovery_query_prebuilt[61]; /** * IPv4 address for IP multicast. * * This constant is big endian (network byte order). */ extern const uint32_t sood_discovery_multicast_ipv4_address_be; /** * IPv4 address for IP multicast. */ extern const uint8_t sood_discovery_multicast_ipv4_address[4]; /** * Destination UDP port for IP multicast and broadcast. */ extern const uint16_t sood_discovery_server_udp_port; #ifdef __cplusplus }
-
-
-
@@ -11,7 +11,122 @@ //// === // // C API. // // Throughout this module, I use `u8` for `char` counterpart. Even though they are not completely // same type, this is suffice in my usecase. I'm not going to support ancient computers or DSP or // whatever. I hate C. // // In order to make comparison easier, structs, enums and functions use same symbols defined in // the header file. They should appear in the same order as possible. If you modified definitions // in this file, update `c.h` too. export fn sood_add(a: i32, b: i32) i32 { return a + b; const std = @import("std"); const sood = @import("lib.zig"); const sood_message_kind = enum(c_int) { SOOD_MESSAGE_UNKNOWN = 0, SOOD_MESSAGE_QUERY = 1, SOOD_MESSAGE_RESPONSE = 2, }; const sood_message = extern struct { raw_ptr: [*]const u8, raw_len: usize, sood_message_kind: sood_message_kind, }; const sood_parse_result = enum(c_int) { SOOD_PARSE_OK = 0, SOOD_PARSE_ERROR_HEADER_SIZE_MISMATCH = 1, SOOD_PARSE_ERROR_INVALID_SIGNATURE = 2, }; export fn sood_parse(dst: *sood_message, ptr: [*]const u8, len: usize) sood_parse_result { const bytes = ptr[0..len]; const message = sood.Message.parse(bytes) catch |err| return switch (err) { sood.Message.Header.ParseError.InvalidHeaderSize => .SOOD_PARSE_ERROR_HEADER_SIZE_MISMATCH, sood.Message.Header.ParseError.InvalidSignature => .SOOD_PARSE_ERROR_INVALID_SIGNATURE, }; dst.raw_ptr = ptr; dst.raw_len = len; dst.sood_message_kind = switch (message.header.kind) { .unknown => .SOOD_MESSAGE_UNKNOWN, .query => .SOOD_MESSAGE_QUERY, .response => .SOOD_MESSAGE_RESPONSE, }; return .SOOD_PARSE_OK; } const sood_message_property = extern struct { key_ptr: [*]const u8, key_len: usize, value_ptr: [*]const u8, value_len: usize, }; const sood_message_read_property_result = enum(c_int) { SOOD_READ_PROPERTY_OK = 0, SOOD_READ_PROPERTY_DONE = 1, SOOD_READ_PROPERTY_ERROR_EMPTY_KEY = 2, SOOD_READ_PROPERTY_ERROR_KEY_SIZE_MISMATCH = 3, SOOD_READ_PROPERTY_ERROR_NON_UTF8_KEY = 4, SOOD_READ_PROPERTY_ERROR_VALUE_SIZE_CORRUPTED = 5, SOOD_READ_PROPERTY_ERROR_VALUE_SIZE_MISMATCH = 6, SOOD_READ_PROPERTY_ERROR_NON_UTF8_VALUE = 7, }; const sood_message_iter = extern struct { msg: *const sood_message, i: usize, }; export fn sood_message_iterator(dst: *sood_message_iter, msg: *const sood_message) void { dst.msg = msg; dst.i = sood.Message.Header.byte_size; } export fn sood_message_iter_next( iter: *sood_message_iter, dst: *sood_message_property, ) sood_message_read_property_result { const bytes = iter.msg.raw_ptr[iter.i..iter.msg.raw_len]; if (bytes.len == 0) { return .SOOD_READ_PROPERTY_DONE; } var p: sood.Message.Property = undefined; iter.i += sood.Message.readProperty(bytes, &p) catch |err| return switch (err) { sood.Message.ReadPropertyError.EmptyKey => .SOOD_READ_PROPERTY_ERROR_EMPTY_KEY, sood.Message.ReadPropertyError.IncompleteKey => .SOOD_READ_PROPERTY_ERROR_KEY_SIZE_MISMATCH, sood.Message.ReadPropertyError.NonUTF8Key => .SOOD_READ_PROPERTY_ERROR_NON_UTF8_KEY, sood.Message.ReadPropertyError.InvalidValueSizeField => .SOOD_READ_PROPERTY_ERROR_VALUE_SIZE_CORRUPTED, sood.Message.ReadPropertyError.NonUTF8Value => .SOOD_READ_PROPERTY_ERROR_NON_UTF8_VALUE, sood.Message.ReadPropertyError.IncompleteValue => .SOOD_READ_PROPERTY_ERROR_VALUE_SIZE_MISMATCH, }; dst.key_ptr = p.key.ptr; dst.key_len = p.key.len; dst.value_ptr = p.value.ptr; dst.value_len = p.value.len; return .SOOD_READ_PROPERTY_OK; } export const sood_discovery_query_prebuilt = sood.discovery.Query.prebuilt.*; export const sood_discovery_multicast_ipv4_address_be = std.mem.readInt(u32, &sood.discovery.multicast_ipv4_address, .big); export const sood_discovery_multicast_ipv4_address = sood.discovery.multicast_ipv4_address; export const sood_discovery_server_udp_port: u16 = sood.discovery.udp_port; test "sood_prebuilt_discovery_query size matches" { const testing = @import("std").testing; try testing.expectEqual( // This MUST be the same value as one in `c.h`. 61, sood_discovery_query_prebuilt.len, ); }
-
-
-
@@ -20,4 +20,5 @@ test {_ = @import("Message.zig"); _ = @import("discovery.zig"); _ = @import("ipv4_subnet.zig"); _ = @import("c.zig"); }
-