pax_global_header00006660000000000000000000000064150702353210014510gustar00rootroot0000000000000052 comment=9a0c965708df457b6b45bf21af01e0b763646f66 vali-0.1.0/000077500000000000000000000000001507023532100124415ustar00rootroot00000000000000vali-0.1.0/.build.yml000066400000000000000000000011601507023532100143370ustar00rootroot00000000000000image: alpine/latest packages: - aml-dev - json-c-dev - meson # for docs - go - zip sources: - https://gitlab.freedesktop.org/emersion/vali.git artifacts: - docs.zip tasks: - setup: | cd vali meson setup build/ --fatal-meson-warnings - build: | cd vali ninja -C build/ - docs: | cd vali go install 'codeberg.org/emersion/gyosu@latest' ~/go/bin/gyosu \ -fexported-symbols='vali_*' -fexported-symbols='VALI_*' \ -ffile-prefix-map="include/"= \ -fsite-name=vali \ -o public \ include/vali.h zip -r ~/docs.zip public/ vali-0.1.0/.editorconfig000066400000000000000000000002211507023532100151110ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true indent_style = tab indent_size = 4 vali-0.1.0/.gitignore000066400000000000000000000000441507023532100144270ustar00rootroot00000000000000/subprojects/* !/subprojects/*.wrap vali-0.1.0/.gitlab-ci.yml000066400000000000000000000001601507023532100150720ustar00rootroot00000000000000include: https://gitlab.freedesktop.org/emersion/dalligi/-/raw/master/templates/single.yml build: pages: true vali-0.1.0/LICENSE000066400000000000000000000020641507023532100134500ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2024 Simon Ser Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vali-0.1.0/README.md000066400000000000000000000036561507023532100137320ustar00rootroot00000000000000# vali A [Varlink] C implementation and code generator. ## Building vali depends on [json-c] and [aml]. To compile vali, run these commands: meson setup build/ ninja -C build/ ## Library API The library API is documented in the [header] and can be browsed on the [website]. ### Client To open a connection: ```c struct vali_client *client = vali_client_connect_unix("/run/org.example.ftl"); ``` ### Service To listen on a Unix socket: ```c struct vali_service *service = vali_service_create(); vali_service_set_call_handler(service, handler); vali_service_listen_unix(service, "/run/org.example.ftl"); ``` Read below for information about creating a handler. ## Code generation Given a Varlink definition file: ```varlink interface org.example.ftl method Jump(latitude: float, longitude: float) -> () ``` A header and a source file can be generated: ```shell vali generate --prefix=ftl org.example.ftl.varlink org.example.ftl.h org.example.ftl.c ``` The generated code exposes client methods: ```c const struct ftl_Jump_in in = { .latitude = 37.56, .longitude = 126.99, }; ftl_Jump(client, &in, NULL, NULL); ``` It also contains a call handler implementing the Varlink service: ```c static void handle_jump(struct ftl_Jump_service_call call, const struct ftl_Jump_in *in) { printf("Jump: latitude=%f longitude=%f\n", in->latitude, in->longitude); ftl_Jump_close_with_reply(call, NULL); } static const struct ftl_handler ftl_handler = { .Jump = handle_jump, }; int main(int argc, char *argv[]) { struct vali_service *service = vali_service_create(); vali_service_set_call_handler(service, ftl_get_call_handler(&ftl_handler)); return 0; } ``` See [`example/`] for a more complete example. ## License MIT [Varlink]: https://varlink.org/ [json-c]: https://github.com/json-c/json-c [aml]: https://github.com/any1/aml [header]: ./include/vali.h [website]: https://emersion.pages.freedesktop.org/vali [`example/`]: ./example/ vali-0.1.0/client.c000066400000000000000000000121251507023532100140640ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "vali.h" #include "conn.h" struct vali_client_call { struct vali_client *client; }; struct vali_client { struct vali_conn conn; struct vali_client_call *pending_call; }; struct vali_client *vali_client_connect_fd(int fd) { struct vali_client *client = calloc(1, sizeof(*client)); if (client == NULL) { return NULL; } conn_init(&client->conn, fd); return client; } struct vali_client *vali_client_connect_unix(const char *path) { struct sockaddr_un addr; if (!set_sockaddr_un(&addr, path)) { errno = -EINVAL; return false; } int fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { return NULL; } if (!set_cloexec(fd)) { goto err_fd; } if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { goto err_fd; } struct vali_client *client = vali_client_connect_fd(fd); if (client == NULL) { goto err_fd; } return client; err_fd: close(fd); return NULL; } void vali_client_destroy(struct vali_client *client) { conn_finish(&client->conn); free(client); } static bool client_send(struct vali_client *client, struct json_object *req) { return conn_enqueue(&client->conn, req) && conn_flush(&client->conn); } static bool client_receive(struct vali_client *client, struct json_object **out, struct vali_error *err, bool *continues) { if (out != NULL) { *out = NULL; } if (err != NULL) { *err = (struct vali_error){0}; } bool eof = false; struct json_object *resp = NULL; if (!conn_receive(&client->conn, &eof) || !conn_dequeue(&client->conn, &resp) || resp == NULL) { return false; } struct json_object *params = json_object_object_get(resp, "parameters"); const char *error_name = json_object_get_string(json_object_object_get(resp, "error")); *continues = json_object_get_boolean(json_object_object_get(resp, "continues")); bool ok = error_name == NULL; if (out != NULL && ok) { *out = json_object_get(params); } if (err != NULL && !ok) { vali_error_set(err, error_name, params); } json_object_put(resp); return ok; } bool vali_client_call(struct vali_client *client, const char *method, struct json_object *in, struct json_object **out, struct vali_error *err) { assert(in == NULL || json_object_get_type(in) == json_type_object); struct json_object *req = json_object_new_object(); struct json_object *method_obj = json_object_new_string(method); if (req == NULL || method_obj == NULL) { return false; } json_object_object_add(req, "method", method_obj); json_object_object_add(req, "parameters", in); bool continues = false; return client_send(client, req) && client_receive(client, out, err, &continues) && !continues; } struct vali_client_call *vali_client_call_more(struct vali_client *client, const char *method, struct json_object *in) { assert(in == NULL || json_object_get_type(in) == json_type_object); struct json_object *req = json_object_new_object(); struct json_object *method_obj = json_object_new_string(method); struct json_object *more_obj = json_object_new_boolean(true); if (req == NULL || method_obj == NULL || more_obj == NULL) { return NULL; } json_object_object_add(req, "method", method_obj); json_object_object_add(req, "parameters", in); json_object_object_add(req, "more", more_obj); if (!client_send(client, req)) { return NULL; } struct vali_client_call *call = calloc(1, sizeof(*call)); if (call == NULL) { return NULL; } call->client = client; assert(client->pending_call == NULL); client->pending_call = call; return call; } bool vali_client_call_oneway(struct vali_client *client, const char *method, struct json_object *in) { assert(in == NULL || json_object_get_type(in) == json_type_object); struct json_object *req = json_object_new_object(); struct json_object *method_obj = json_object_new_string(method); struct json_object *oneway_obj = json_object_new_boolean(true); if (req == NULL || method_obj == NULL || oneway_obj == NULL) { return false; } json_object_object_add(req, "method", method_obj); json_object_object_add(req, "parameters", in); json_object_object_add(req, "oneway", oneway_obj); return client_send(client, req); } void vali_error_finish(struct vali_error *err) { free(err->name); json_object_put(err->parameters); } void vali_error_set(struct vali_error *err, const char *name, struct json_object *parameters) { vali_error_finish(err); *err = (struct vali_error){ .name = strdup(name), .parameters = json_object_get(parameters), }; } bool vali_client_call_wait(struct vali_client_call *call, struct json_object **out, struct vali_error *err) { assert(!vali_client_call_is_done(call)); bool continues = false; if (!client_receive(call->client, out, err, &continues)) { return false; } if (!continues) { call->client->pending_call = NULL; } return true; } void vali_client_call_destroy(struct vali_client_call *call) { assert(vali_client_call_is_done(call)); free(call); } bool vali_client_call_is_done(struct vali_client_call *call) { return call->client->pending_call != call; } vali-0.1.0/conn.c000066400000000000000000000070571507023532100135530ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "conn.h" #include "vali.h" static bool buffer_grow(struct vali_buffer *buf, size_t n) { if (buf->cap >= buf->size + n) { return true; } size_t new_cap = buf->cap; if (new_cap == 0) { new_cap = 4096; } while (new_cap < buf->size + n) { new_cap *= 2; } char *new_data = realloc(buf->data, new_cap); if (new_data == NULL) { return false; } buf->data = new_data; buf->cap = new_cap; return true; } static void buffer_shift(struct vali_buffer *buf, size_t n) { if (n == buf->size) { buf->size = 0; } else { memmove(buf->data, &buf->data[n], buf->size - n); buf->size -= n; } } void conn_init(struct vali_conn *conn, int fd) { *conn = (struct vali_conn){ .fd = fd }; } void conn_finish(struct vali_conn *conn) { close(conn->fd); free(conn->in.data); free(conn->out.data); } bool conn_enqueue(struct vali_conn *conn, struct json_object *obj) { const char *raw = json_object_to_json_string(obj); size_t raw_size = strlen(raw) + 1; if (!buffer_grow(&conn->out, raw_size)) { return false; } memcpy(&conn->out.data[conn->out.size], raw, raw_size); conn->out.size += raw_size; json_object_put(obj); return true; } bool conn_flush(struct vali_conn *conn) { size_t n_written = 0; bool ok = true; while (n_written < conn->out.size) { ssize_t n = write(conn->fd, &conn->out.data[n_written], conn->out.size - n_written); if (n < 0) { if (errno == EINTR) { continue; } else if (errno == EAGAIN) { break; } ok = false; break; } n_written += (size_t)n; } buffer_shift(&conn->out, n_written); return ok; } static ssize_t conn_find_in_buf_end(struct vali_conn *conn, size_t offset, size_t len) { for (size_t i = offset; i < offset + len; i++) { if (conn->in.data[i] == '\0') { return (ssize_t)i; } } return -1; } bool conn_receive(struct vali_conn *conn, bool *eof) { *eof = false; if (conn_find_in_buf_end(conn, 0, conn->in.size) >= 0) { return true; } while (true) { if (!buffer_grow(&conn->in, 4096)) { return false; } size_t max = conn->in.cap - conn->in.size; ssize_t n = read(conn->fd, &conn->in.data[conn->in.size], max); if (n < 0) { if (errno == EINTR) { continue; } else if (errno == EAGAIN) { break; } return false; } else if (n == 0) { *eof = true; break; } conn->in.size += (size_t)n; if (conn_find_in_buf_end(conn, conn->in.size - (size_t)n, (size_t)n) >= 0) { break; } } return true; } bool conn_dequeue(struct vali_conn *conn, struct json_object **out) { *out = NULL; ssize_t end = conn_find_in_buf_end(conn, 0, conn->in.size); if (end < 0) { return true; } struct json_object *obj = json_tokener_parse(conn->in.data); size_t msg_size = (size_t)end + 1; buffer_shift(&conn->in, msg_size); *out = obj; return true; } bool set_sockaddr_un(struct sockaddr_un *addr, const char *path) { *addr = (struct sockaddr_un){ .sun_family = AF_UNIX }; size_t path_len = strlen(path); if (path_len >= sizeof(addr->sun_path)) { return false; } memcpy(addr->sun_path, path, path_len + 1); return true; } bool set_cloexec(int fd) { int flags = fcntl(fd, F_GETFD, 0); if (flags < 0) { return false; } flags |= FD_CLOEXEC; return fcntl(fd, F_SETFD, flags) != -1; } bool set_nonblock(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { return false; } flags |= O_NONBLOCK; return fcntl(fd, F_SETFL, flags) != -1; } vali-0.1.0/def.c000066400000000000000000000371331507023532100133520ustar00rootroot00000000000000#include #include #include #include #include "def.h" #include "util.h" struct str_builder { struct array arr; }; static void str_builder_write_ch(struct str_builder *sb, char ch) { char *ptr = array_add(&sb->arr, sizeof(*ptr)); *ptr = ch; } static void str_builder_write_str(struct str_builder *sb, const char *str) { size_t len = strlen(str); char *ptr = array_add(&sb->arr, len); memcpy(ptr, str, len); } static char *str_builder_commit(struct str_builder *sb) { str_builder_write_ch(sb, '\0'); char *str = sb->arr.data; sb->arr = (struct array){0}; return str; } struct reader { FILE *f; int last_ch; int line; }; static void log_error(struct reader *r, const char *msg, ...) { fprintf(stderr, "line %d: ", r->line); va_list args; va_start(args, msg); vfprintf(stderr, msg, args); va_end(args); fprintf(stderr, "\n"); } static int read_ch(struct reader *r) { int ch = fgetc(r->f); if (ch == EOF && ferror(r->f)) { perror("Failed to read file"); } if (ch == '\n') { r->line++; } r->last_ch = ch; return ch; } static void unread_ch(struct reader *r) { assert(r->last_ch != EOF); if (r->last_ch == '\n') { r->line--; } ungetc(r->last_ch, r->f); r->last_ch = EOF; } static bool skip_whitespace(struct reader *r) { while (1) { int ch = read_ch(r); if (ch == EOF) { return feof(r->f); } switch (ch) { case ' ': case '\t': case '\r': case '\n': break; // skip default: unread_ch(r); return true; } } } static char *read_comment(struct reader *r) { if (!skip_whitespace(r)) { return false; } int ch = read_ch(r); if (ch == EOF) { return NULL; } else if (ch != '#') { unread_ch(r); return NULL; } struct str_builder sb = {0}; str_builder_write_ch(&sb, (char)ch); while (1) { int ch = read_ch(r); if (ch == EOF) { if (feof(r->f)) { break; } else { free(str_builder_commit(&sb)); return NULL; } } str_builder_write_ch(&sb, (char)ch); if (ch == '\n') { break; } } return str_builder_commit(&sb); } static bool skip_whitespace_and_comments(struct reader *r) { while (1) { if (!skip_whitespace(r)) { return false; } char *comment = read_comment(r); if (comment == NULL) { return true; } free(comment); } } static char *read_token(struct reader *r) { if (!skip_whitespace_and_comments(r)) { return NULL; } struct str_builder sb = {0}; while (1) { int ch = read_ch(r); if (ch == EOF) { if (feof(r->f) && sb.arr.size > 0) { return str_builder_commit(&sb); } else { free(str_builder_commit(&sb)); return NULL; } } switch (ch) { case '?': case '(': case ')': case ',': case ':': if (sb.arr.size > 0) { unread_ch(r); } else { str_builder_write_ch(&sb, (char)ch); } return str_builder_commit(&sb); case ']': case '>': str_builder_write_ch(&sb, (char)ch); return str_builder_commit(&sb); case ' ': case '\t': case '\r': case '\n': case '#': unread_ch(r); return str_builder_commit(&sb); default: str_builder_write_ch(&sb, (char)ch); } } } static bool expect_token(struct reader *r, const char *want) { char *got = read_token(r); if (got == NULL) { return false; } bool ok = strcmp(got, want) == 0; if (!ok) { log_error(r, "expected \"%s\", got \"%s\"", want, got); } free(got); return ok; } static bool is_alpha(char ch) { return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); } static bool is_alpha_num(char ch) { return is_alpha(ch) || (ch >= '0' && ch <= '9'); } static bool is_interface_name(const char *str) { // TODO: be more strict if (!is_alpha(str[0])) { return false; } for (size_t i = 0; str[i] != '\0'; i++) { char ch = str[i]; if (!is_alpha_num(ch) && ch != '-' && ch != '.') { return false; } } return true; } static bool is_name(const char *str) { if (str[0] < 'A' || str[0] > 'Z') { return false; } for (size_t i = 0; str[i] != '\0'; i++) { if (!is_alpha_num(str[i])) { return false; } } return true; } static bool is_field_name(const char *str) { if (!is_alpha(str[0])) { return false; } for (size_t i = 0; str[i] != '\0'; i++) { if (!is_alpha_num(str[i]) && str[i] != '_') { return false; } } return true; } static char *read_interface_name(struct reader *r) { char *tok = read_token(r); if (tok == NULL) { return NULL; } else if (!is_interface_name(tok)) { log_error(r, "\"%s\" is not a valid interface name", tok); free(tok); return NULL; } return tok; } static char *read_name(struct reader *r) { char *tok = read_token(r); if (tok == NULL) { return NULL; } else if (!is_name(tok)) { log_error(r, "\"%s\" is not a valid name", tok); free(tok); return NULL; } return tok; } static bool parse_basic_type(enum varlink_kind *out, const char *str) { if (strcmp(str, "bool") == 0) { *out = VARLINK_BOOL; } else if (strcmp(str, "int") == 0) { *out = VARLINK_INT; } else if (strcmp(str, "float") == 0) { *out = VARLINK_FLOAT; } else if (strcmp(str, "string") == 0) { *out = VARLINK_STRING; } else if (strcmp(str, "object") == 0) { *out = VARLINK_OBJECT; } else { return false; } return true; } static bool read_struct_or_enum(struct reader *r, struct varlink_type *out); static bool read_element_type(struct reader *r, const char *tok, struct varlink_type *out) { enum varlink_kind kind; if (parse_basic_type(&kind, tok)) { *out = (struct varlink_type){ .kind = kind }; return true; } if (strcmp(tok, "(") == 0) { unread_ch(r); return read_struct_or_enum(r, out); } if (is_name(tok)) { *out = (struct varlink_type){ .kind = VARLINK_NAME, .name = strdup(tok) }; return true; } log_error(r, "expected element type, got \"%s\"", tok); return false; } static bool read_type(struct reader *r, struct varlink_type *out) { char *tok = read_token(r); if (tok == NULL) { return false; } bool nullable = strcmp(tok, "?") == 0; if (nullable) { free(tok); tok = read_token(r); if (tok == NULL) { return false; } } enum varlink_kind kind; if (strcmp(tok, "[]") == 0) { kind = VARLINK_ARRAY; } else if (strcmp(tok, "[string]") == 0) { kind = VARLINK_MAP; } else { bool ok = read_element_type(r, tok, out); free(tok); if (!ok) { return false; } out->nullable = nullable; return true; } free(tok); struct varlink_type *inner = calloc(1, sizeof(*inner)); if (inner == NULL) { return false; } if (!read_type(r, inner)) { free(inner); return false; } *out = (struct varlink_type){ .kind = kind, .inner = inner, .nullable = nullable }; return true; } static bool read_struct_or_enum(struct reader *r, struct varlink_type *out) { if (!expect_token(r, "(")) { return false; } struct array enum_entries = {0}; struct array field_names = {0}; struct array field_types = {0}; struct varlink_type typ = {0}; while (1) { char *tok = read_token(r); if (tok == NULL) { return false; } else if (strcmp(tok, ")") == 0 && typ.kind == 0) { // empty parentheses free(tok); typ.kind = VARLINK_STRUCT; break; } else if (!is_field_name(tok)) { log_error(r, "expected field name, got \"%s\"", tok); return false; } char *name = tok; char *sep = read_token(r); if (sep == NULL) { return false; } if (typ.kind == 0) { if (strcmp(sep, ",") == 0 || strcmp(sep, ")") == 0) { typ.kind = VARLINK_ENUM; } else if (strcmp(sep, ":") == 0) { typ.kind = VARLINK_STRUCT; } else { log_error(r, "expected one of \",\", \")\" or \":\", got \"%s\"", sep); return false; } } else { switch (typ.kind) { case VARLINK_ENUM: if (strcmp(sep, ",") != 0 && strcmp(sep, ")") != 0) { log_error(r, "expected one of \",\" or \")\", got \"%s\"", sep); return false; } break; case VARLINK_STRUCT: if (strcmp(sep, ":") != 0) { log_error(r, "expected \":\", got \"%s\"", sep); return false; } break; default: abort(); // unreachable } } switch (typ.kind) { case VARLINK_ENUM:; char **entry = array_add(&enum_entries, sizeof(*entry)); *entry = name; if (strcmp(sep, ")") == 0) { free(sep); goto out; } break; case VARLINK_STRUCT:; struct varlink_type t; if (!read_type(r, &t)) { return false; } char **name_ptr = array_add(&field_names, sizeof(*name_ptr)); struct varlink_type *t_ptr = array_add(&field_types, sizeof(*t_ptr)); *name_ptr = name; *t_ptr = t; free(sep); sep = read_token(r); if (sep == NULL) { return false; } if (strcmp(sep, ")") == 0) { free(sep); goto out; } else if (strcmp(sep, ",") != 0) { log_error(r, "expected \",\" or \")\", got \"%s\"", sep); return false; } break; default: abort(); // unreachable } free(sep); } out: typ.enum_ = (struct varlink_enum){ .entries = enum_entries.data, .entries_len = enum_entries.size / sizeof(char *), }; typ.struct_ = (struct varlink_struct){ .field_names = field_names.data, .field_types = field_types.data, .fields_len = field_names.size / sizeof(char *), }; *out = typ; return true; } static bool read_struct(struct reader *r, struct varlink_struct *out) { struct varlink_type t; if (!read_struct_or_enum(r, &t)) { return false; } else if (t.kind != VARLINK_STRUCT) { log_error(r, "expected struct, got enum"); return false; } *out = t.struct_; return true; } static bool check_type(const struct varlink_interface *iface, const struct varlink_type *t); static bool check_struct(const struct varlink_interface *iface, const struct varlink_struct *s) { for (size_t i = 0; i < s->fields_len; i++) { if (!check_type(iface, &s->field_types[i])) { return false; } } return true; } static bool check_type(const struct varlink_interface *iface, const struct varlink_type *t) { switch (t->kind) { case VARLINK_STRUCT: return check_struct(iface, &t->struct_); case VARLINK_NAME: for (size_t i = 0; i < iface->type_aliases_len; i++) { if (strcmp(t->name, iface->type_aliases[i].name) == 0) { return true; } } fprintf(stderr, "unknown type '%s'\n", t->name); return false; case VARLINK_ARRAY: case VARLINK_MAP: return check_type(iface, t->inner); default: return true; } } static bool check_interface(const struct varlink_interface *iface) { for (size_t i = 0; i < iface->type_aliases_len; i++) { if (!check_type(iface, &iface->type_aliases[i].type)) { return false; } } for (size_t i = 0; i < iface->methods_len; i++) { const struct varlink_method *method = &iface->methods[i]; if (!check_struct(iface, &method->in) || !check_struct(iface, &method->out)) { return false; } } for (size_t i = 0; i < iface->errors_len; i++) { if (!check_struct(iface, &iface->errors[i].struct_)) { return false; } } return true; } static char *read_description(struct reader *r) { struct str_builder sb = {0}; while (1) { int prev_line = r->line; if (!skip_whitespace(r)) { free(str_builder_commit(&sb)); return NULL; } if (r->line != prev_line) { sb.arr.size = 0; } char *comment = read_comment(r); if (comment == NULL) { break; } assert(comment[0] == '#'); const char *text = &comment[1]; if (text[0] == ' ') { text = &text[1]; } str_builder_write_str(&sb, text); free(comment); } if (sb.arr.size == 0) { free(str_builder_commit(&sb)); return NULL; } return str_builder_commit(&sb); } static bool read_interface(struct reader *r, struct varlink_interface *out) { char *description = read_description(r); if (!expect_token(r, "interface")) { return false; } char *name = read_interface_name(r); if (name == NULL) { return false; } struct array type_aliases = {0}; struct array methods = {0}; struct array errors = {0}; char *keyword = NULL; while (1) { char *description = read_description(r); keyword = read_token(r); if (keyword == NULL) { break; } if (strcmp(keyword, "type") == 0) { char *name = read_name(r); if (name == NULL) { return false; } struct varlink_type t; if (!read_struct_or_enum(r, &t)) { free(name); return false; } struct varlink_type_alias *type_alias_entry = array_add(&type_aliases, sizeof(*type_alias_entry)); *type_alias_entry = (struct varlink_type_alias){ .name = name, .description = description, .type = t, }; } else if (strcmp(keyword, "method") == 0) { char *name = read_name(r); if (name == NULL) { return false; } struct varlink_struct in; if (!read_struct(r, &in)) { return false; } if (!expect_token(r, "->")) { return false; } struct varlink_struct out; if (!read_struct(r, &out)) { return false; } struct varlink_method *method_entry = array_add(&methods, sizeof(*method_entry)); *method_entry = (struct varlink_method){ .name = name, .description = description, .in = in, .out = out, }; } else if (strcmp(keyword, "error") == 0) { char *name = read_name(r); if (name == NULL) { return false; } struct varlink_struct s; if (!read_struct(r, &s)) { return false; } struct varlink_error *error_entry = array_add(&errors, sizeof(*error_entry)); *error_entry = (struct varlink_error){ .name = name, .description = description, .struct_ = s, }; } else { log_error(r, "expected one of \"type\", \"method\", \"error\", got \"%s\"", keyword); return false; } free(keyword); } free(keyword); struct varlink_interface iface = { .name = name, .description = description, .type_aliases = type_aliases.data, .type_aliases_len = type_aliases.size / sizeof(struct varlink_type_alias), .methods = methods.data, .methods_len = methods.size / sizeof(struct varlink_method), .errors = errors.data, .errors_len = errors.size / sizeof(struct varlink_error), }; if (!check_interface(&iface)) { return false; } *out = iface; return true; } struct varlink_interface *varlink_interface_read(FILE *f) { struct reader r = { .f = f, .last_ch = EOF, .line = 1 }; struct varlink_interface *iface = calloc(1, sizeof(*iface)); if (iface == NULL) { return NULL; } if (!read_interface(&r, iface)) { return NULL; } if (!feof(r.f)) { varlink_interface_destroy(iface); return NULL; } return iface; } static void finish_type(struct varlink_type *t); static void finish_struct(struct varlink_struct *s) { for (size_t i = 0; i < s->fields_len; i++) { free(s->field_names[i]); finish_type(&s->field_types[i]); } free(s->field_names); free(s->field_types); } static void finish_enum(struct varlink_enum *e) { for (size_t i = 0; i < e->entries_len; i++) { free(e->entries[i]); } free(e->entries); } static void finish_type(struct varlink_type *t) { if (t->inner != NULL) { finish_type(t->inner); free(t->inner); } free(t->name); finish_struct(&t->struct_); finish_enum(&t->enum_); } static void finish_method(struct varlink_method *method) { free(method->name); free(method->description); finish_struct(&method->in); finish_struct(&method->out); } static void finish_type_alias(struct varlink_type_alias *type_alias) { free(type_alias->name); free(type_alias->description); finish_type(&type_alias->type); } static void finish_error(struct varlink_error *error) { free(error->name); free(error->description); finish_struct(&error->struct_); } void varlink_interface_destroy(struct varlink_interface *iface) { for (size_t i = 0; i < iface->type_aliases_len; i++) { finish_type_alias(&iface->type_aliases[i]); } free(iface->type_aliases); for (size_t i = 0; i < iface->methods_len; i++) { finish_method(&iface->methods[i]); } free(iface->methods); for (size_t i = 0; i < iface->errors_len; i++) { finish_error(&iface->errors[i]); } free(iface->errors); free(iface->name); free(iface->description); free(iface); } vali-0.1.0/example/000077500000000000000000000000001507023532100140745ustar00rootroot00000000000000vali-0.1.0/example/client.c000066400000000000000000000013711507023532100155200ustar00rootroot00000000000000#include #include #include #include "example.h" int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "usage: client \n"); return 1; } struct vali_client *client = vali_client_connect_unix(argv[1]); if (client == NULL) { perror("Failed to connect"); return 1; } struct ftl_CalculateConfiguration_out out = {0}; ftl_CalculateConfiguration(client, &(struct ftl_CalculateConfiguration_in){ .current = { .longitude = 0, .latitude = 0, .distance = 0 }, .target = { .longitude = 0, .latitude = 0, .distance = 0 }, }, &out, NULL); struct ftl_Jump_in jump_in = { .configuration = out.configuration, }; ftl_Jump(client, &jump_in, NULL, NULL); vali_client_destroy(client); return 0; } vali-0.1.0/example/example.varlink000066400000000000000000000031401507023532100171150ustar00rootroot00000000000000# Interface to jump a spacecraft to another point in space. # The FTL Drive is the propulsion system to achieve # faster-than-light travel through space. A ship making a # properly calculated jump can arrive safely in planetary # orbit, or alongside other ships or spaceborne objects. interface org.example.ftl # The current state of the FTL drive and the amount of # fuel available to jump. type DriveCondition ( state: (idle, spooling, busy), tylium_level: int ) # Speed, trajectory and jump duration is calculated prior # to activating the FTL drive. type DriveConfiguration ( speed: int, trajectory: int, duration: int ) # The galactic coordinates use the Sun as the origin. # Galactic longitude is measured with primary direction # from the Sun to the center of the galaxy in the galactic # plane, while the galactic latitude measures the angle # of the object above the galactic plane. type Coordinate ( longitude: float, latitude: float, distance: int ) # Monitor the drive. The method will reply with an update # whenever the drive's state changes method Monitor() -> (condition: DriveCondition) # Calculate the drive's jump parameters from the current # position to the target position in the galaxy method CalculateConfiguration( current: Coordinate, target: Coordinate ) -> (configuration: DriveConfiguration) # Jump to the calculated point in space method Jump(configuration: DriveConfiguration) -> () # There is not enough tylium to jump with the given # parameters error NotEnoughEnergy () # The supplied parameters are outside the supported range error ParameterOutOfRange (field: string) vali-0.1.0/example/meson.build000066400000000000000000000005321507023532100162360ustar00rootroot00000000000000example_gen = custom_target( 'example_gen', command: [tool, 'generate', '--prefix=ftl', '@INPUT@', '@OUTPUT0@', '@OUTPUT1@'], input: 'example.varlink', output: ['example.h', 'example.c'], ) executable( 'client', ['client.c', example_gen], dependencies: vali, ) executable( 'service', ['service.c', example_gen], dependencies: vali, ) vali-0.1.0/example/service.c000066400000000000000000000037501507023532100157050ustar00rootroot00000000000000#include #include #include #include "example.h" static void handle_monitor(struct ftl_Monitor_service_call call, const struct ftl_Monitor_in *in) { struct ftl_Monitor_out out = { .condition = { .state = ftl_DriveCondition_state_idle, .tylium_level = 42, }, }; ftl_Monitor_close_with_reply(call, &out); } static void handle_calculate_configuration(struct ftl_CalculateConfiguration_service_call call, const struct ftl_CalculateConfiguration_in *in) { struct ftl_CalculateConfiguration_out out = { .configuration = { .speed = 1, .trajectory = 2, .duration = 3, }, }; ftl_CalculateConfiguration_close_with_reply(call, &out); } static void handle_jump(struct ftl_Jump_service_call call, const struct ftl_Jump_in *in) { const struct ftl_DriveConfiguration *config = &in->configuration; printf("Jump: speed=%d, trajectory=%d, duration=%d\n", config->speed, config->trajectory, config->duration); ftl_Jump_close_with_reply(call, NULL); } static const struct ftl_handler ftl_handler = { .Monitor = handle_monitor, .CalculateConfiguration = handle_calculate_configuration, .Jump = handle_jump, }; int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "usage: service \n"); return 1; } const char *socket_path = argv[1]; struct vali_service *service = vali_service_create(); const struct vali_registry_options registry_options = { .vendor = "FooCorp", .product = "example", .version = "1.0", .url = "https://example.org", }; struct vali_registry *registry = vali_registry_create(®istry_options); vali_registry_add(registry, &ftl_interface, ftl_get_call_handler(&ftl_handler)); vali_service_set_call_handler(service, vali_registry_get_call_handler(registry)); unlink(socket_path); if (!vali_service_listen_unix(service, socket_path)) { fprintf(stderr, "Failed to listen on Unix socket %s\n", socket_path); return 1; } while (vali_service_dispatch(service)); vali_service_destroy(service); return 0; } vali-0.1.0/include/000077500000000000000000000000001507023532100140645ustar00rootroot00000000000000vali-0.1.0/include/conn.h000066400000000000000000000012361507023532100151740ustar00rootroot00000000000000#ifndef CONN_H #define CONN_H #include struct json_object; struct sockaddr_un; struct vali_buffer { char *data; size_t size, cap; }; struct vali_conn { int fd; struct vali_buffer in, out; }; void conn_init(struct vali_conn *conn, int fd); void conn_finish(struct vali_conn *conn); bool conn_enqueue(struct vali_conn *conn, struct json_object *obj); bool conn_flush(struct vali_conn *conn); bool conn_receive(struct vali_conn *conn, bool *eof); bool conn_dequeue(struct vali_conn *conn, struct json_object **out); bool set_sockaddr_un(struct sockaddr_un *addr, const char *path); bool set_cloexec(int fd); bool set_nonblock(int fd); #endif vali-0.1.0/include/def.h000066400000000000000000000025721507023532100150010ustar00rootroot00000000000000#ifndef VARLINK_DEF_H #define VARLINK_DEF_H #include #include #include struct varlink_interface { char *name; char *description; struct varlink_type_alias *type_aliases; size_t type_aliases_len; struct varlink_method *methods; size_t methods_len; struct varlink_error *errors; size_t errors_len; }; struct varlink_enum { char **entries; size_t entries_len; }; struct varlink_struct { char **field_names; struct varlink_type *field_types; size_t fields_len; }; struct varlink_method { char *name; char *description; struct varlink_struct in, out; }; enum varlink_kind { VARLINK_STRUCT = 1, VARLINK_ENUM, VARLINK_NAME, VARLINK_BOOL, VARLINK_INT, VARLINK_FLOAT, VARLINK_STRING, VARLINK_OBJECT, VARLINK_ARRAY, VARLINK_MAP, }; struct varlink_type { enum varlink_kind kind; bool nullable; struct varlink_type *inner; // for VARLINK_ARRAY and VARLINK_MAP char *name; // for VARLINK_NAME struct varlink_struct struct_; // for VARLINK_STRUCT struct varlink_enum enum_; // for VARLINK_ENUM }; struct varlink_type_alias { char *name; char *description; struct varlink_type type; // only structs and enums }; struct varlink_error { char *name; char *description; struct varlink_struct struct_; }; struct varlink_interface *varlink_interface_read(FILE *f); void varlink_interface_destroy(struct varlink_interface *iface); #endif vali-0.1.0/include/util.h000066400000000000000000000003071507023532100152120ustar00rootroot00000000000000#ifndef UTIL_H #define UTIL_H #include struct array { size_t size, cap; void *data; }; void *array_add(struct array *arr, size_t size); void array_finish(struct array *arr); #endif vali-0.1.0/include/vali.h000066400000000000000000000235021507023532100151720ustar00rootroot00000000000000#ifndef VALI_H #define VALI_H #include struct json_object; /** * A Varlink call error. * * Resources held by this struct can be released with vali_error_finish(). */ struct vali_error { // The fully-qualified error name char *name; struct json_object *parameters; }; /** * A call for a client. */ struct vali_client_call; /** * Wait for a reply from the service. * * If out is NULL, the call result is discarded. If out is non-NULL, it's * filled with the call result on success, and the caller is responsible for * releasing the object. * * If err is NULL, any Varlink error is discarded. If err is non-NULL, it's * always filled: either reset to zero if there is no Varlink error, or * populated if the service returns an error. The caller is responsible for * releasing the error via vali_error_finish(). * * true is returned on success, false is returned on error (either a Varlink * error, or a connection error). */ bool vali_client_call_wait(struct vali_client_call *call, struct json_object **out, struct vali_error *err); /** * Destroy a client call. */ void vali_client_call_destroy(struct vali_client_call *call); /** * Check whether no more replies are expected for this call. */ bool vali_client_call_is_done(struct vali_client_call *call); /** * A Varlink client. */ struct vali_client; /** * Connect to a Varlink service's Unix socket. */ struct vali_client *vali_client_connect_unix(const char *path); /** * Create a new Varlink client from an existing FD. */ struct vali_client *vali_client_connect_fd(int fd); /** * Destroy a Varlink client. */ void vali_client_destroy(struct vali_client *client); /** * Call a Varlink method. * * This function blocks until the call reply has been received. * * The method argument needs to contain the fully qualified method name. * * in may be NULL if the method takes no parameters. * * If out is NULL, the call result is discarded. If out is non-NULL, it's * filled with the call result on success, and the caller is responsible for * releasing the object. * * If err is NULL, any Varlink error is discarded. If err is non-NULL, it's * always filled: either reset to zero if there is no Varlink error, or * populated if the service returns an error. The caller is responsible for * releasing the error via vali_error_finish(). * * true is returned on success, false is returned on error (either a Varlink * error, or a connection error). */ bool vali_client_call(struct vali_client *client, const char *method, struct json_object *in, struct json_object **out, struct vali_error *err); /** * Send a Varlink call request, and indicate that multiple replies are * expected. * * This function blocks until the request has been sent. Only a single call can * be pending at a time. * * The method argument needs to contain the fully qualified method name. * * in may be NULL if the method takes no parameters. * * NULL is returned on connection error. * * After calling this function, clients should call vali_client_call_wait() * while vali_client_call_has_more() returns true, then call * vali_client_call_destroy(). */ struct vali_client_call *vali_client_call_more(struct vali_client *client, const char *method, struct json_object *in); /** * Send a Varlink call request, and indicate that no reply is expected. * * See vali_client_call(). */ bool vali_client_call_oneway(struct vali_client *client, const char *method, struct json_object *in); /** * A call for a service. */ struct vali_service_call; /** * Retrieve user-specified data attached to the call. */ void *vali_service_call_get_user_data(const struct vali_service_call *call); /** * Attach user-specified data to the call. */ void vali_service_call_set_user_data(struct vali_service_call *call, void *user_data); /** * Get the service from a call. */ struct vali_service *vali_service_call_get_service(struct vali_service_call *call); /** * Get the connection a call was issued from. * * If the connection has been destroyed (e.g. because the client has * disconnected), NULL is returned. */ struct vali_service_conn *vali_service_call_get_conn(struct vali_service_call *call); /** * Get the fully-qualified method name. */ const char *vali_service_call_get_method(const struct vali_service_call *call); /** * Get the input parameters for a call. */ struct json_object *vali_service_call_get_parameters(const struct vali_service_call *call); /** * Check if the client has requested the server to not reply. * * Call handlers still need to call vali_service_call_close_with_reply() (but * can pass in NULL parameters). */ bool vali_service_call_is_oneway(const struct vali_service_call *call); /** * Check if the client has requested the server to send multiple replies. * * Services can use vali_service_call_reply() to send continued replies, and * must call vali_service_call_close_with_reply() for the final reply. */ bool vali_service_call_is_more(const struct vali_service_call *call); /** * Send the final reply to a call. * * This transfers ownership of the parameters to this function. * * The struct vali_service_call pointer becomes invalid. */ void vali_service_call_close_with_reply(struct vali_service_call *call, struct json_object *params); /** * Send an error to a call. * * This transfers ownership of the parameters to this function. * * The struct vali_service_call pointer becomes invalid. */ void vali_service_call_close_with_error(struct vali_service_call *call, const char *error, struct json_object *params); /** * Send a continued reply to a call. * * This transfers ownership of the parameters to this function. * * The call must return true for vali_service_call_is_more(). */ void vali_service_call_reply(struct vali_service_call *call, struct json_object *params); /** * A Varlink service call handler. * * The user_data field is passed to the callback. * * A handler must eventually finish the passed-in call, by calling * vali_service_call_close_with_reply() or vali_service_call_close_with_error(). * A handler may return before finishing the call (ie, may asynchronously * finish the call). */ struct vali_service_call_handler { void (*func)(struct vali_service_call *call, void *user_data); void *user_data; }; /** * A Varlink service (ie, the server side). */ struct vali_service; /** * Create a new Varlink service. */ struct vali_service *vali_service_create(void); /** * Destroy a Varlink service. */ void vali_service_destroy(struct vali_service *service); /** * Retrieve user-specified data attached to the service. */ void *vali_service_get_user_data(struct vali_service *service); /** * Attach user-specified data to the service. */ void vali_service_set_user_data(struct vali_service *service, void *user_data); /** * Get a FD signalling when the service needs to be dispatched. * * The returned FD becomes readable when vali_service_dispatch() needs to * be called. This is useful to integrate the service in another event loop. */ int vali_service_get_fd(struct vali_service *service); /** * Set the service's call handler. * * A handler must be set before the service starts listening. * * Any previously set handler is replaced. */ void vali_service_set_call_handler(struct vali_service *service, struct vali_service_call_handler handler); /** * Listen on a Unix socket. */ bool vali_service_listen_unix(struct vali_service *service, const char *path); /** * Listen on an already-opened Unix socket FD. */ bool vali_service_listen_fd(struct vali_service *service, int fd); /** * A connection for a service. * * This object is owned by the struct vali_service and is destroyed by * vali_service_dispatch() when the remote client is disconnected. */ struct vali_service_conn; /** * Create a new connection to a service with an already-opened FD. */ struct vali_service_conn *vali_service_create_conn(struct vali_service *service, int fd); /** * Disconnect a remote client. */ void vali_service_conn_disconnect(struct vali_service_conn *conn); /** * Get the underlying connection FD. */ int vali_service_conn_get_fd(struct vali_service_conn *conn); /** * Dispatch I/O events. */ bool vali_service_dispatch(struct vali_service *service); /** * A Varlink interface registry. * * A registry is a helper to implement a Varlink service. It dispatches * incoming method calls to the right interface. It also implements the * "org.varlink.service" interface. * * To make a struct vali_service use a registry, pass the result of * vali_registry_get_call_handler() to * vali_service_set_call_handler(). */ struct vali_registry; struct vali_registry_options { const char *vendor; const char *product; const char *version; const char *url; }; /** * Create a new registry. */ struct vali_registry *vali_registry_create(const struct vali_registry_options *options); /** * Destroy a registry. */ void vali_registry_destroy(struct vali_registry *registry); /** * Get a call handler for the registry. * * This can be passed to vali_service_set_call_handler() to dispatch * incoming requests to the registry. */ struct vali_service_call_handler vali_registry_get_call_handler(struct vali_registry *registry); struct vali_registry_interface { const char *name; const char *definition; }; /** * Register an interface. * * An interface can only be registered once. The "org.varlink.service" * interface cannot be registered. */ bool vali_registry_add(struct vali_registry *registry, const struct vali_registry_interface *iface, struct vali_service_call_handler handler); /** * Release resources held by an error. * * This function can be called safely on a zero error. */ void vali_error_finish(struct vali_error *err); /** * Set an error's name and parameters. * * This replaces any previously set error name and parameters. */ void vali_error_set(struct vali_error *err, const char *name, struct json_object *parameters); #endif vali-0.1.0/meson.build000066400000000000000000000036171507023532100146120ustar00rootroot00000000000000project( 'vali', 'c', version: '0.1.0', license: 'MIT', meson_version: '>=1.0', default_options: [ 'c_std=c11', 'warning_level=3', 'werror=true', 'wrap_mode=nodownload', ], ) cc = meson.get_compiler('c') add_project_arguments([ '-D_POSIX_C_SOURCE=200809L', ], language: 'c') add_project_arguments(cc.get_supported_arguments([ '-Wundef', '-Wmissing-prototypes', '-Walloca', '-Wconversion', '-Wno-unused-parameter', '-Wno-missing-field-initializers', '-Werror=implicit', ]), language: 'c') inc = include_directories('include') jsonc = dependency('json-c') aml = dependency('aml') add_project_arguments([ '-DHAVE_AML_V1=@0@'.format(aml.version().version_compare('>=1.0.0').to_int()), '-DAML_UNSTABLE_API=1', # for aml < 1.0.0 ], language: 'c') tool = executable( 'vali', ['tool.c', 'def.c', 'util.c'], include_directories: inc, install: true, ) org_varlink_service = custom_target( 'service_iface', command: [tool, 'generate', '@INPUT@', '@OUTPUT0@', '@OUTPUT1@'], input: 'org.varlink.service.varlink', output: ['org.varlink.service.h', 'org.varlink.service.c'], ) version = meson.project_version() version_major = version.split('.')[0] version_minor = version.split('.')[1] assert(version_major == '0') lib = library( 'vali', ['client.c', 'service.c', 'registry.c', 'conn.c', 'util.c', org_varlink_service], include_directories: inc, dependencies: [jsonc, aml], install: true, version: version, soversion: version_minor, ) install_headers(['include/vali.h']) pkgconfig = import('pkgconfig') pkgconfig.generate( lib, name: 'vali', description: 'Varlink generator', requires: [jsonc], variables: [ 'tool=${bindir}/vali', ], ) vali = declare_dependency( link_with: [lib], dependencies: [jsonc], include_directories: inc, variables: [ 'tool=vali', ], ) meson.override_find_program('vali', tool) meson.override_dependency('vali', vali) subdir('example') subdir('test') vali-0.1.0/org.varlink.service.varlink000066400000000000000000000020401507023532100177200ustar00rootroot00000000000000# The Varlink Service Interface is provided by every varlink service. It # describes the service and the interfaces it implements. interface org.varlink.service # Get a list of all the interfaces a service provides and information # about the implementation. method GetInfo() -> ( vendor: string, product: string, version: string, url: string, interfaces: []string ) # Get the description of an interface that is implemented by this service. method GetInterfaceDescription(interface: string) -> (description: string) # The requested interface was not found. error InterfaceNotFound (interface: string) # The requested method was not found error MethodNotFound (method: string) # The interface defines the requested method, but the service does not # implement it. error MethodNotImplemented (method: string) # One of the passed parameters is invalid. error InvalidParameter (parameter: string) # Client is denied access error PermissionDenied () # Method is expected to be called with 'more' set to true, but wasn't error ExpectedMore () vali-0.1.0/registry.c000066400000000000000000000132641507023532100144630ustar00rootroot00000000000000#include #include #include #include "util.h" #include "vali.h" #include "org.varlink.service.h" struct vali_registry { char *vendor; char *product; char *version; char *url; struct array interfaces; // struct vali_registry_entry }; struct vali_registry_entry { const struct vali_registry_interface *interface; struct vali_service_call_handler call_handler; }; static const struct vali_registry_entry *registry_get(struct vali_registry *registry, const char *iface_name, size_t iface_name_len) { const struct vali_registry_entry *interfaces = registry->interfaces.data; size_t interfaces_len = registry->interfaces.size / sizeof(interfaces[0]); for (size_t i = 0; i < interfaces_len; i++) { const struct vali_registry_entry *iface = &interfaces[i]; if (strncmp(iface->interface->name, iface_name, iface_name_len) == 0) { return iface; } } return NULL; } static void handle_GetInfo(struct org_varlink_service_GetInfo_service_call call, const struct org_varlink_service_GetInfo_in *in) { struct vali_registry *registry = vali_service_call_get_user_data(call.base); struct org_varlink_service_GetInfo_out out = { .vendor = registry->vendor, .product = registry->product, .version = registry->version, .url = registry->url, }; const struct vali_registry_entry *interfaces = registry->interfaces.data; size_t interfaces_len = registry->interfaces.size / sizeof(interfaces[0]); out.interfaces.data = calloc(interfaces_len, sizeof(out.interfaces.data[0])); if (out.interfaces.data == NULL) { vali_service_conn_disconnect(vali_service_call_get_conn(call.base)); return; } for (size_t i = 0; i < interfaces_len; i++) { out.interfaces.data[i] = (char *)interfaces[i].interface->name; out.interfaces.len++; } org_varlink_service_GetInfo_close_with_reply(call, &out); free(out.interfaces.data); } static void handle_GetInterfaceDescription(struct org_varlink_service_GetInterfaceDescription_service_call call, const struct org_varlink_service_GetInterfaceDescription_in *in) { struct vali_registry *registry = vali_service_call_get_user_data(call.base); const struct vali_registry_entry *iface = registry_get(registry, in->interface, strlen(in->interface)); if (iface == NULL) { vali_service_conn_disconnect(vali_service_call_get_conn(call.base)); return; // TODO: send proper error } struct org_varlink_service_GetInterfaceDescription_out out = { .description = (char *)iface->interface->definition, }; org_varlink_service_GetInterfaceDescription_close_with_reply(call, &out); } static const struct org_varlink_service_handler org_varlink_service_handler = { .GetInfo = handle_GetInfo, .GetInterfaceDescription = handle_GetInterfaceDescription, }; static void org_varlink_service_handle_call(struct vali_service_call *call, void *user_data) { struct vali_registry *registry = user_data; vali_service_call_set_user_data(call, registry); struct vali_service_call_handler call_handler = org_varlink_service_get_call_handler(&org_varlink_service_handler); call_handler.func(call, call_handler.user_data); } struct vali_registry *vali_registry_create(const struct vali_registry_options *options) { struct vali_registry *registry = calloc(1, sizeof(*registry)); if (registry == NULL) { return NULL; } registry->vendor = strdup(options->vendor); registry->product = strdup(options->product); registry->version = strdup(options->version); registry->url = strdup(options->url); if (registry->vendor == NULL || registry->product == NULL || registry->version == NULL || registry->url == NULL) { vali_registry_destroy(registry); return NULL; } const struct vali_service_call_handler call_handler = { .func = org_varlink_service_handle_call, .user_data = registry, }; if (!vali_registry_add(registry, &org_varlink_service_interface, call_handler)) { vali_registry_destroy(registry); return NULL; } return registry; } void vali_registry_destroy(struct vali_registry *registry) { array_finish(®istry->interfaces); free(registry->vendor); free(registry->product); free(registry->version); free(registry->url); free(registry); } static void registry_handle_call(struct vali_service_call *call, void *user_data) { struct vali_registry *registry = user_data; const char *method = vali_service_call_get_method(call); const char *last_dot = strrchr(method, '.'); if (last_dot == NULL) { vali_service_conn_disconnect(vali_service_call_get_conn(call)); return; // TODO: send proper error } size_t iface_name_len = (size_t)(last_dot - method); const struct vali_registry_entry *iface = registry_get(registry, method, iface_name_len); if (iface != NULL) { iface->call_handler.func(call, iface->call_handler.user_data); return; } char *iface_name = strndup(method, iface_name_len); if (iface_name == NULL) { vali_service_conn_disconnect(vali_service_call_get_conn(call)); return; } const struct org_varlink_service_error_InterfaceNotFound params = { .interface = iface_name, }; org_varlink_service_error_InterfaceNotFound_close_service_call(call, ¶ms); free(iface_name); } struct vali_service_call_handler vali_registry_get_call_handler(struct vali_registry *registry) { return (struct vali_service_call_handler){ .func = registry_handle_call, .user_data = registry, }; } bool vali_registry_add(struct vali_registry *registry, const struct vali_registry_interface *iface, struct vali_service_call_handler call_handler) { if (registry_get(registry, iface->name, strlen(iface->name))) { abort(); } struct vali_registry_entry *dst = array_add(®istry->interfaces, sizeof(*dst)); if (dst == NULL) { return false; } *dst = (struct vali_registry_entry){ .interface = iface, .call_handler = call_handler, }; return true; } vali-0.1.0/service.c000066400000000000000000000252251507023532100142530ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "conn.h" #include "vali.h" #include "util.h" #if HAVE_AML_V1 #define AML_HANDLER_ARG struct aml_handler * #else #define AML_HANDLER_ARG void * #endif struct vali_service { struct aml *event_loop; struct vali_service_call_handler call_handler; struct array listen_handlers; // struct aml_handler * void *user_data; }; struct vali_service_conn { struct vali_conn base; struct aml_handler *handler; struct vali_service *service; struct vali_service_call *pending_call; }; struct vali_service_call { struct vali_service_conn *conn; // NULL if disconnected char *method; struct json_object *params; bool oneway, more; void *user_data; }; struct vali_service *vali_service_create(void) { struct vali_service *service = calloc(1, sizeof(*service)); if (service == NULL) { return NULL; } service->event_loop = aml_new(); if (service->event_loop == NULL) { free(service); return NULL; } return service; } void vali_service_destroy(struct vali_service *service) { // TODO: close client connections struct aml_handler **listen_handlers = service->listen_handlers.data; size_t listen_handlers_len = service->listen_handlers.size / sizeof(listen_handlers[0]); for (size_t i = 0; i < listen_handlers_len; i++) { struct aml_handler *handler = listen_handlers[i]; aml_stop(service->event_loop, handler); close(aml_get_fd(handler)); aml_unref(handler); } array_finish(&service->listen_handlers); aml_unref(service->event_loop); free(service); } void *vali_service_get_user_data(struct vali_service *service) { return service->user_data; } void vali_service_set_user_data(struct vali_service *service, void *user_data) { service->user_data = user_data; } int vali_service_get_fd(struct vali_service *service) { return aml_get_fd(service->event_loop); } void vali_service_set_call_handler(struct vali_service *service, struct vali_service_call_handler handler) { service->call_handler = handler; } static bool conn_dequeue_request(struct vali_service_conn *conn, struct vali_service_call **out) { *out = NULL; struct json_object *req; if (!conn_dequeue(&conn->base, &req)) { return false; } else if (req == NULL) { return true; } const char *method = json_object_get_string(json_object_object_get(req, "method")); struct json_object *params = json_object_object_get(req, "parameters"); bool oneway = json_object_get_boolean(json_object_object_get(req, "oneway")); bool more = json_object_get_boolean(json_object_object_get(req, "more")); if (method == NULL) { goto err_req; } struct vali_service_call *call = calloc(1, sizeof(*call)); if (call == NULL) { goto err_req; } *call = (struct vali_service_call){ .conn = conn, .method = strdup(method), .params = json_object_get(params), .oneway = oneway, .more = more, }; if (call->method == NULL) { free(call); goto err_req; } json_object_put(req); *out = call; return true; err_req: json_object_put(req); return false; } static void conn_destroy(struct vali_service_conn *conn) { if (conn->pending_call != NULL) { conn->pending_call->conn = NULL; } aml_stop(conn->service->event_loop, conn->handler); aml_unref(conn->handler); conn_finish(&conn->base); free(conn); } static void conn_update_event_mask(struct vali_service_conn *conn) { uint32_t mask = 0; if (conn->pending_call == NULL) { mask |= AML_EVENT_READ; } if (conn->base.out.size > 0) { mask |= AML_EVENT_WRITE; } aml_set_event_mask(conn->handler, mask); } static void conn_handle_event(AML_HANDLER_ARG data) { struct vali_service_conn *conn = aml_get_userdata(data); uint32_t revents = aml_get_revents(conn->handler); bool eof = false; if (revents & AML_EVENT_READ) { if (!conn_receive(&conn->base, &eof)) { goto error; } while (conn->pending_call == NULL) { struct vali_service_call *call = NULL; if (!conn_dequeue_request(conn, &call)) { goto error; } else if (call == NULL) { break; } conn->pending_call = call; conn->service->call_handler.func(call, conn->service->call_handler.user_data); } } if (revents & AML_EVENT_WRITE) { if (!conn_flush(&conn->base)) { goto error; } } conn_update_event_mask(conn); if (eof && conn->base.in.size == 0 && conn->base.out.size == 0 && conn->pending_call == NULL) { conn_destroy(conn); } return; error: conn_destroy(conn); } struct vali_service_conn *vali_service_create_conn(struct vali_service *service, int fd) { if (!set_cloexec(fd) || !set_nonblock(fd)) { return NULL; } struct vali_service_conn *conn = calloc(1, sizeof(*conn)); if (conn == NULL) { return NULL; } // TODO: limit conn buffer size conn_init(&conn->base, fd); conn->service = service; conn->handler = aml_handler_new(fd, conn_handle_event, conn, NULL); if (conn->handler == NULL) { goto err_conn; } if (aml_start(service->event_loop, conn->handler) < 0) { goto err_handler; } aml_set_event_mask(conn->handler, AML_EVENT_READ); return conn; err_handler: aml_unref(conn->handler); err_conn: free(conn); return NULL; } void vali_service_conn_disconnect(struct vali_service_conn *conn) { // TODO: handle errors somehow? shutdown(conn->base.fd, SHUT_RDWR); } int vali_service_conn_get_fd(struct vali_service_conn *conn) { return conn->base.fd; } static void listener_handle_readable(AML_HANDLER_ARG data) { struct aml_handler *listen_handler = data; struct vali_service *service = aml_get_userdata(listen_handler); int client_fd = accept(aml_get_fd(listen_handler), NULL, NULL); if (client_fd < 0) { return; } struct vali_service_conn *conn = vali_service_create_conn(service, client_fd); if (conn == NULL) { close(client_fd); return; } } bool vali_service_listen_unix(struct vali_service *service, const char *path) { if (service->call_handler.func == NULL) { abort(); } struct sockaddr_un addr; if (!set_sockaddr_un(&addr, path)) { errno = -EINVAL; return false; } int fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { return false; } if (!set_cloexec(fd) || !set_nonblock(fd)) { goto err_fd; } if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { goto err_fd; } if (listen(fd, 128) < 0) { goto err_fd; } if (!vali_service_listen_fd(service, fd)) { goto err_fd; } return true; err_fd: close(fd); return false; } bool vali_service_listen_fd(struct vali_service *service, int fd) { struct aml_handler *handler = aml_handler_new(fd, listener_handle_readable, service, NULL); if (handler == NULL) { return false; } if (aml_start(service->event_loop, handler) < 0) { goto err_handler; } struct aml_handler **handler_ptr = array_add(&service->listen_handlers, sizeof(*handler_ptr)); if (handler_ptr == NULL) { goto err_stop; } *handler_ptr = handler; aml_set_event_mask(handler, AML_EVENT_READ); return true; err_stop: aml_stop(service->event_loop, handler); err_handler: aml_unref(handler); return false; } bool vali_service_dispatch(struct vali_service *service) { if (aml_poll(service->event_loop, -1) < 0) { return false; } aml_dispatch(service->event_loop); return true; } void *vali_service_call_get_user_data(const struct vali_service_call *call) { return call->user_data; } void vali_service_call_set_user_data(struct vali_service_call *call, void *user_data) { call->user_data = user_data; } struct vali_service *vali_service_call_get_service(struct vali_service_call *call) { return call->conn->service; } struct vali_service_conn *vali_service_call_get_conn(struct vali_service_call *call) { return call->conn; } const char *vali_service_call_get_method(const struct vali_service_call *call) { return call->method; } struct json_object *vali_service_call_get_parameters(const struct vali_service_call *call) { return call->params; } bool vali_service_call_is_oneway(const struct vali_service_call *call) { return call->oneway; } bool vali_service_call_is_more(const struct vali_service_call *call) { return call->more; } static void service_call_destroy(struct vali_service_call *call) { if (call->conn != NULL) { assert(call->conn->pending_call == call); call->conn->pending_call = NULL; } json_object_put(call->params); free(call->method); free(call); } static void service_call_disconnect(struct vali_service_call *call) { struct vali_service_conn *conn = call->conn; service_call_destroy(call); if (conn != NULL) { vali_service_conn_disconnect(conn); } } static void service_call_close_with_response(struct vali_service_call *call, struct json_object *resp) { struct vali_service_conn *conn = call->conn; if (conn == NULL) { json_object_put(resp); return; } if (call->oneway) { json_object_put(resp); } else { if (!conn_enqueue(&conn->base, resp)) { vali_service_conn_disconnect(conn); } } service_call_destroy(call); conn_update_event_mask(conn); } void vali_service_call_close_with_reply(struct vali_service_call *call, struct json_object *params) { assert(params == NULL || json_object_get_type(params) == json_type_object); struct json_object *resp = NULL; if (call->oneway) { json_object_put(params); } else { resp = json_object_new_object(); if (resp == NULL) { service_call_disconnect(call); return; } json_object_object_add(resp, "parameters", params); } service_call_close_with_response(call, resp); } void vali_service_call_close_with_error(struct vali_service_call *call, const char *error, struct json_object *params) { struct json_object *resp = NULL; if (call->oneway) { json_object_put(params); } else { resp = json_object_new_object(); struct json_object *error_obj = json_object_new_string(error); if (resp == NULL || error_obj == NULL) { service_call_disconnect(call); return; } json_object_object_add(resp, "error", error_obj); json_object_object_add(resp, "parameters", params); } service_call_close_with_response(call, resp); } void vali_service_call_reply(struct vali_service_call *call, struct json_object *params) { assert(call->more); assert(params == NULL || json_object_get_type(params) == json_type_object); if (call->conn == NULL) { return; } struct json_object *resp = NULL; if (call->oneway) { json_object_put(params); } else { resp = json_object_new_object(); struct json_object *continues_obj = json_object_new_boolean(true); if (resp == NULL || continues_obj == NULL) { vali_service_conn_disconnect(call->conn); return; } json_object_object_add(resp, "parameters", params); json_object_object_add(resp, "continues", continues_obj); } if (!conn_enqueue(&call->conn->base, resp)) { vali_service_conn_disconnect(call->conn); } conn_update_event_mask(call->conn); } vali-0.1.0/subprojects/000077500000000000000000000000001507023532100150045ustar00rootroot00000000000000vali-0.1.0/subprojects/aml.wrap000066400000000000000000000001011507023532100164400ustar00rootroot00000000000000[wrap-git] url = https://github.com/any1/aml.git revision = HEAD vali-0.1.0/test/000077500000000000000000000000001507023532100134205ustar00rootroot00000000000000vali-0.1.0/test/basic.c000066400000000000000000000101351507023532100146450ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "test-gen.h" static bool running = true; static void handle_echo(struct test_Echo_service_call call, const struct test_Echo_in *in) { const struct test_Echo_out out = { .s = in->s, .a = { .data = in->a.data, .len = in->a.len, }, .m = { .data = (struct test_Echo_out_m_entry *)in->m.data, .len = in->m.len, }, }; test_Echo_close_with_reply(call, &out); } static void handle_count_until(struct test_CountUntil_service_call call, const struct test_CountUntil_in *in) { assert(vali_service_call_is_more(call.base)); for (int i = 0; i < in->n; i++) { test_CountUntil_reply(call, &(struct test_CountUntil_out){ .n = i }); } test_CountUntil_close_with_reply(call, &(struct test_CountUntil_out){ .n = in->n }); } static void handle_stop(struct test_Stop_service_call call, const struct test_Stop_in *in) { running = false; test_Stop_close_with_reply(call, NULL); } static const struct test_handler test_handler = { .Echo = handle_echo, .CountUntil = handle_count_until, .Stop = handle_stop, }; static void run_service(int fd) { struct vali_service *service = vali_service_create(); const struct vali_registry_options registry_options = { .vendor = "FooCorp", .product = "example", .version = "1.0", .url = "https://example.org", }; struct vali_registry *registry = vali_registry_create(®istry_options); vali_registry_add(registry, &test_interface, test_get_call_handler(&test_handler)); vali_service_set_call_handler(service, vali_registry_get_call_handler(registry)); struct vali_service_conn *service_conn = vali_service_create_conn(service, fd); assert(service_conn != NULL); while (running && vali_service_dispatch(service)); vali_service_dispatch(service); // flush reply vali_service_destroy(service); vali_registry_destroy(registry); } static void test_echo(struct vali_client *client) { const struct test_Echo_in in = { .s = { .foo = 42, .bar = "bar", .baz = &(enum test_Enum){test_Enum_b}, }, .a = { .data = (bool[]){4, 2}, .len = 2, }, .m = { .data = (struct test_Echo_in_m_entry[]){ { .key = "one", .value = 1 }, { .key = "two", .value = 2 }, { .key = "three", .value = 3 }, }, .len = 3, }, }; struct test_Echo_out out = {0}; bool ok = test_Echo(client, &in, &out, NULL); assert(ok); assert(out.s.foo == in.s.foo); assert(strcmp(out.s.bar, in.s.bar) == 0); assert(*out.s.baz == *in.s.baz); assert(out.a.len == in.a.len); for (size_t i = 0; i < out.a.len; i++) { assert(out.a.data[i] == in.a.data[i]); } assert(out.m.len == in.m.len); for (size_t i = 0; i < out.m.len; i++) { assert(strcmp(out.m.data[i].key, in.m.data[i].key) == 0); assert(out.m.data[i].value == in.m.data[i].value); } test_Echo_out_finish(&out); } static void test_count_until(struct vali_client *client) { const struct test_CountUntil_in in = { .n = 10, }; struct test_CountUntil_client_call call = test_CountUntil_more(client, &in); assert(call.base != NULL); for (int i = 0; i <= in.n; i++) { assert(!vali_client_call_is_done(call.base)); struct test_CountUntil_out out = {0}; bool ok = test_CountUntil_client_call_wait(call, &out, NULL); assert(ok); assert(out.n == i); } assert(vali_client_call_is_done(call.base)); vali_client_call_destroy(call.base); } int main(int argc, char *argv[]) { #ifdef NDEBUG fprintf(stderr, "NDEBUG must be disabled for tests\n"); return 1; #endif int sockets[2] = { -1, -1 }; int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); assert(ret == 0); pid_t pid = fork(); assert(pid >= 0); if (pid == 0) { close(sockets[1]); run_service(sockets[0]); _exit(0); } close(sockets[0]); struct vali_client *client = vali_client_connect_fd(sockets[1]); assert(client != NULL); test_echo(client); test_count_until(client); bool ok = test_Stop(client, NULL, NULL, NULL); assert(ok); vali_client_destroy(client); int stat = 0; ret = waitpid(pid, &stat, 0); assert(ret >= 0); assert(WIFEXITED(stat) && WEXITSTATUS(stat) == 0); return 0; } vali-0.1.0/test/certification.c000066400000000000000000000631331507023532100164150ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "certification-gen.h" struct service_state { uint64_t prev_client_num; // TODO: handle multiple clients char *client_id; int prev_test_num; }; static const struct cert_Test01_out service_test01_out = { .bool_ = true, }; static const struct cert_Test02_out service_test02_out = { .int_ = 42, }; static const struct cert_Test03_out service_test03_out = { .float_ = 12.34, }; static const struct cert_Test04_out service_test04_out = { .string = "foo", }; static const struct cert_Test05_out service_test05_out = { .bool_ = false, .int_ = 12, .float_ = 34.56, .string = "bar", }; static const struct cert_Test06_out service_test06_out = { .struct_ = { .bool_ = true, .int_ = 98, .float_ = 76.54, .string = "baz", }, }; static const struct cert_Test07_out service_test07_out = { .map = { .data = (struct cert_Test07_out_map_entry[]){ { .key = "Julie", .value = "Renate" }, { .key = "Aksel", .value = "Anders" }, { .key = "Eivind", .value = "Hebert" }, }, .len = 3, }, }; static const struct cert_Test08_out service_test08_out = { .set = { .data = (struct cert_Test08_out_set_entry[]){ { .key = "Nora" }, { .key = "Gustav" }, { .key = "Lilleaas" }, { .key = "Rachel" }, }, .len = 4, }, }; static struct cert_Test09_out service_test09_out = { .mytype = { .object = NULL, .enum_ = cert_MyType_enum_two, .struct_ = { .first = 42, .second = "foo", }, .array = { .data = (char *[]){ "Anders", "Thomas", "Rebekka", "Tove", "Renate", }, .len = 5, }, .dictionary = { .data = (struct cert_MyType_dictionary_entry[]){ { .key = "Julie", .value = "Renate" }, { .key = "Aksel", .value = "Anders" }, { .key = "Eivind", .value = "Hebert" }, }, .len = 3, }, .stringset = { .data = (struct cert_MyType_stringset_entry[]){ { .key = "Nora" }, { .key = "Gustav" }, { .key = "Lilleaas" }, { .key = "Rachel" }, }, .len = 4, }, .nullable = NULL, .nullable_array_struct = &(struct cert_MyType_nullable_array_struct){ .data = (struct cert_MyType_nullable_array_struct_entry[]){ { .first = 2006, .second = "Reprise" }, { .first = 2011, .second = "Oslo, 31. august" }, { .first = 2021, .second = "Verdens verste menneske" }, }, .len = 3, }, .interface = { .foo = &(struct cert_Interface_foo){ .data = (struct cert_Interface_foo_entry *[]){ NULL, &(struct cert_Interface_foo_entry){ .data = (struct cert_Interface_foo_entry_entry[]){ { .key = "one", .value = cert_Interface_foo_entry_value_baz }, }, .len = 1, }, &(struct cert_Interface_foo_entry){ .data = (struct cert_Interface_foo_entry_entry[]){ { .key = "two", .value = cert_Interface_foo_entry_value_foo }, { .key = "three", .value = cert_Interface_foo_entry_value_bar }, }, .len = 2, }, NULL, }, .len = 4, }, .anon = { .foo = true, .bar = false, }, }, }, }; static char *const service_test10_replies[] = { "Anders", "Thomas", "Rebekka", "Tove", "Renate", }; static const size_t service_test10_replies_len = sizeof(service_test10_replies) / sizeof(service_test10_replies[0]); static const char usage[] = "usage: certification client|service Run the client/service against an external implementation\n" " certification self-test Run the client against the internal service\n"; static void print_client_error(const char *method, const struct vali_error *err) { fprintf(stderr, "%s() failed", method); if (err->name != NULL) { const char *raw_params = json_object_to_json_string_ext(err->parameters, JSON_C_TO_STRING_PRETTY); fprintf(stderr, ": %s %s", err->name, raw_params); } fprintf(stderr, "\n"); } static bool run_client(struct vali_client *client) { struct vali_error err = {0}; struct cert_Start_out start_out = {0}; if (!cert_Start(client, NULL, &start_out, &err)) { print_client_error("Start", &err); return false; } char *client_id = start_out.client_id; const struct cert_Test01_in test01_in = { .client_id = client_id, }; struct cert_Test01_out test01_out = {0}; if (!cert_Test01(client, &test01_in, &test01_out, &err)) { print_client_error("Test01", &err); return false; } const struct cert_Test02_in test02_in = { .client_id = client_id, .bool_ = test01_out.bool_, }; struct cert_Test02_out test02_out = {0}; if (!cert_Test02(client, &test02_in, &test02_out, &err)) { print_client_error("Test02", &err); return false; } const struct cert_Test03_in test03_in = { .client_id = client_id, .int_ = test02_out.int_, }; struct cert_Test03_out test03_out = {0}; if (!cert_Test03(client, &test03_in, &test03_out, &err)) { print_client_error("Test03", &err); return false; } const struct cert_Test04_in test04_in = { .client_id = client_id, .float_ = test03_out.float_, }; struct cert_Test04_out test04_out = {0}; if (!cert_Test04(client, &test04_in, &test04_out, &err)) { print_client_error("Test04", &err); return false; } const struct cert_Test05_in test05_in = { .client_id = client_id, .string = test04_out.string, }; struct cert_Test05_out test05_out = {0}; if (!cert_Test05(client, &test05_in, &test05_out, &err)) { print_client_error("Test05", &err); return false; } const struct cert_Test06_in test06_in = { .client_id = client_id, .bool_ = test05_out.bool_, .int_ = test05_out.int_, .float_ = test05_out.float_, .string = test05_out.string, }; struct cert_Test06_out test06_out = {0}; if (!cert_Test06(client, &test06_in, &test06_out, &err)) { print_client_error("Test06", &err); return false; } const struct cert_Test07_in test07_in = { .client_id = client_id, .struct_ = { .bool_ = test06_out.struct_.bool_, .int_ = test06_out.struct_.int_, .float_ = test06_out.struct_.float_, .string = test06_out.struct_.string, }, }; struct cert_Test07_out test07_out = {0}; if (!cert_Test07(client, &test07_in, &test07_out, &err)) { print_client_error("Test07", &err); return false; } const struct cert_Test08_in test08_in = { .client_id = client_id, .map = { .data = (struct cert_Test08_in_map_entry *)test07_out.map.data, .len = test07_out.map.len, }, }; struct cert_Test08_out test08_out = {0}; if (!cert_Test08(client, &test08_in, &test08_out, &err)) { print_client_error("Test08", &err); return false; } const struct cert_Test09_in test09_in = { .client_id = client_id, .set = { .data = (struct cert_Test09_in_set_entry *)test08_out.set.data, .len = test08_out.set.len, }, }; struct cert_Test09_out test09_out = {0}; if (!cert_Test09(client, &test09_in, &test09_out, &err)) { print_client_error("Test09", &err); return false; } struct cert_Test10_in test10_in = { .client_id = client_id, .mytype = test09_out.mytype, }; struct cert_Test10_client_call call = cert_Test10_more(client, &test10_in); if (call.base == NULL) { fprintf(stderr, "Test10() failed: send error\n"); return false; } char *test10_replies[64] = {0}; size_t test10_replies_len = 0; while (!vali_client_call_is_done(call.base)) { struct cert_Test10_out test10_out = {0}; if (!cert_Test10_client_call_wait(call, &test10_out, &err)) { print_client_error("Test10", &err); return false; } assert(test10_replies_len < sizeof(test10_replies) / sizeof(test10_replies[0])); test10_replies[test10_replies_len++] = test10_out.string; } vali_client_call_destroy(call.base); struct cert_Test11_in test11_in = { .client_id = client_id, .last_more_replies = { .data = test10_replies, .len = test10_replies_len, }, }; if (!cert_Test11_oneway(client, &test11_in)) { fprintf(stderr, "Test11() failed: send error\n"); return false; } struct cert_End_in end_in = { .client_id = client_id, }; struct cert_End_out end_out = {0}; if (!cert_End(client, &end_in, &end_out, &err)) { print_client_error("End", &err); return false; } if (!end_out.all_ok) { fprintf(stderr, "End() indicates missing calls\n"); } cert_End_out_finish(&end_out); for (size_t i = 0; i < test10_replies_len; i++) { free(test10_replies[i]); } cert_Test09_out_finish(&test09_out); cert_Test08_out_finish(&test08_out); cert_Test07_out_finish(&test07_out); cert_Test06_out_finish(&test06_out); cert_Test05_out_finish(&test05_out); cert_Test04_out_finish(&test04_out); cert_Test03_out_finish(&test03_out); cert_Test02_out_finish(&test02_out); cert_Test01_out_finish(&test01_out); cert_Start_out_finish(&start_out); vali_client_destroy(client); return true; } static struct service_state *service_state_from_call(struct vali_service_call *call) { return vali_service_get_user_data(vali_service_call_get_service(call)); } static bool service_check_client_id(struct service_state *state, const char *client_id) { return state->client_id != NULL && strcmp(state->client_id, client_id) == 0; } static bool service_bump_test_num(struct service_state *state, int test_num) { if (state->prev_test_num != test_num - 1) { return false; } state->prev_test_num = test_num; return true; } static void handle_start(struct cert_Start_service_call call, const struct cert_Start_in *in) { struct service_state *state = service_state_from_call(call.base); state->prev_client_num++; char client_id[128]; snprintf(client_id, sizeof(client_id), "vali-%"PRIu64, state->prev_client_num); free(state->client_id); state->client_id = strdup(client_id); state->prev_test_num = 0; cert_Start_close_with_reply(call, &(struct cert_Start_out){ .client_id = client_id, }); } static void handle_test01(struct cert_Test01_service_call call, const struct cert_Test01_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } if (!service_bump_test_num(state, 1)) { // TODO: populate got/wants cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } cert_Test01_close_with_reply(call, &service_test01_out); } static void handle_test02(struct cert_Test02_service_call call, const struct cert_Test02_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } if (in->bool_ != service_test01_out.bool_ || !service_bump_test_num(state, 2)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } cert_Test02_close_with_reply(call, &service_test02_out); } static void handle_test03(struct cert_Test03_service_call call, const struct cert_Test03_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } if (in->int_ != service_test02_out.int_ || !service_bump_test_num(state, 3)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } cert_Test03_close_with_reply(call, &service_test03_out); } static void handle_test04(struct cert_Test04_service_call call, const struct cert_Test04_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } if (in->float_ != service_test03_out.float_ || !service_bump_test_num(state, 4)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } cert_Test04_close_with_reply(call, &service_test04_out); } static void handle_test05(struct cert_Test05_service_call call, const struct cert_Test05_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } if (strcmp(in->string, service_test04_out.string) != 0 || !service_bump_test_num(state, 5)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } cert_Test05_close_with_reply(call, &service_test05_out); } static void handle_test06(struct cert_Test06_service_call call, const struct cert_Test06_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } if (in->bool_ != service_test05_out.bool_ || in->int_ != service_test05_out.int_ || in->float_ != service_test05_out.float_ || strcmp(in->string, service_test05_out.string) != 0 || !service_bump_test_num(state, 6)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } cert_Test06_close_with_reply(call, &service_test06_out); } static void handle_test07(struct cert_Test07_service_call call, const struct cert_Test07_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } if (in->struct_.bool_ != service_test06_out.struct_.bool_ || in->struct_.int_ != service_test06_out.struct_.int_ || in->struct_.float_ != service_test06_out.struct_.float_ || strcmp(in->struct_.string, service_test06_out.struct_.string) != 0 || !service_bump_test_num(state, 7)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } cert_Test07_close_with_reply(call, &service_test07_out); } static void handle_test08(struct cert_Test08_service_call call, const struct cert_Test08_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } bool ok = in->map.len == service_test07_out.map.len; if (ok) { for (size_t i = 0; i < service_test07_out.map.len; i++) { const struct cert_Test07_out_map_entry want = service_test07_out.map.data[i]; bool found = false; for (size_t j = 0; j < in->map.len; j++) { if (strcmp(in->map.data[j].key, want.key) == 0 && strcmp(in->map.data[j].value, want.value) == 0) { found = true; break; } } if (!found) { ok = false; break; } } } if (!ok || !service_bump_test_num(state, 8)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } cert_Test08_close_with_reply(call, &service_test08_out); } static void handle_test09(struct cert_Test09_service_call call, const struct cert_Test09_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } bool ok = in->set.len == service_test08_out.set.len; if (ok) { for (size_t i = 0; i < service_test08_out.set.len; i++) { const char *want = service_test08_out.set.data[i].key; bool found = false; for (size_t j = 0; j < in->set.len; j++) { if (strcmp(in->set.data[j].key, want) == 0) { found = true; break; } } if (!found) { ok = false; break; } } } if (!ok || !service_bump_test_num(state, 9)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } // TODO: find a better way to do this json_object_put(service_test09_out.mytype.object); service_test09_out.mytype.object = json_object_new_int(42); cert_Test09_close_with_reply(call, &service_test09_out); } static void handle_test10(struct cert_Test10_service_call call, const struct cert_Test10_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } const struct cert_MyType *got = &in->mytype; const struct cert_MyType *want = &service_test09_out.mytype; bool ok = json_object_equal(got->object, want->object) && got->enum_ == want->enum_ && got->struct_.first == want->struct_.first && strcmp(got->struct_.second, want->struct_.second) == 0 && got->array.len == want->array.len && got->dictionary.len == want->dictionary.len && got->stringset.len == want->stringset.len && got->nullable == NULL && want->nullable == NULL && got->nullable_array_struct != NULL && got->nullable_array_struct->len == want->nullable_array_struct->len && got->interface.foo != NULL && got->interface.foo->len == want->interface.foo->len && got->interface.anon.foo == want->interface.anon.foo && got->interface.anon.bar == want->interface.anon.bar; if (ok) { for (size_t i = 0; i < want->array.len; i++) { if (strcmp(got->array.data[i], want->array.data[i]) != 0) { ok = false; } } for (size_t i = 0; i < want->dictionary.len; i++) { const struct cert_MyType_dictionary_entry want_entry = want->dictionary.data[i]; bool found = false; for (size_t j = 0; j < got->dictionary.len; j++) { const struct cert_MyType_dictionary_entry got_entry = got->dictionary.data[i]; if (strcmp(got_entry.key, want_entry.key) == 0 && strcmp(got_entry.value, want_entry.value) == 0) { found = true; break; } } if (!found) { ok = false; } } for (size_t i = 0; i < want->stringset.len; i++) { const char *want_key = want->stringset.data[i].key; bool found = false; for (size_t j = 0; j < got->stringset.len; j++) { if (strcmp(got->stringset.data[j].key, want_key) == 0) { found = true; break; } } if (!found) { ok = false; } } for (size_t i = 0; i < want->nullable_array_struct->len; i++) { const struct cert_MyType_nullable_array_struct_entry *want_entry = &want->nullable_array_struct->data[i]; const struct cert_MyType_nullable_array_struct_entry *got_entry = &got->nullable_array_struct->data[i]; if (want_entry->first != got_entry->first || strcmp(want_entry->second, got_entry->second) != 0) { ok = false; } } for (size_t i = 0; i < want->interface.foo->len; i++) { const struct cert_Interface_foo_entry *want_entry = want->interface.foo->data[i]; const struct cert_Interface_foo_entry *got_entry = got->interface.foo->data[i]; if (want_entry == NULL) { if (got_entry != NULL) { ok = false; } break; } if (got_entry->len != want_entry->len) { ok = false; break; } for (size_t j = 0; j < want_entry->len; j++) { const struct cert_Interface_foo_entry_entry want_nested_entry = want_entry->data[j]; bool found = false; for (size_t k = 0; k < got_entry->len; k++) { const struct cert_Interface_foo_entry_entry got_nested_entry = got_entry->data[k]; if (strcmp(got_nested_entry.key, want_nested_entry.key) == 0 && got_nested_entry.value == want_nested_entry.value) { found = true; break; } } if (!found) { ok = false; } } } } if (!ok || !service_bump_test_num(state, 10)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } if (!vali_service_call_is_more(call.base)) { vali_service_conn_disconnect(vali_service_call_get_conn(call.base)); return; } for (size_t i = 0; i < service_test10_replies_len; i++) { struct cert_Test10_out out = { .string = service_test10_replies[i] }; if (i < service_test10_replies_len - 1) { cert_Test10_reply(call, &out); } else { cert_Test10_close_with_reply(call, &out); } } } static void handle_test11(struct cert_Test11_service_call call, const struct cert_Test11_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } bool ok = in->last_more_replies.len == service_test10_replies_len; if (ok) { for (size_t i = 0; i < service_test10_replies_len; i++) { if (strcmp(in->last_more_replies.data[i], service_test10_replies[i]) != 0) { ok = false; break; } } } if (!ok || !service_bump_test_num(state, 11)) { cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0}); return; } if (!vali_service_call_is_oneway(call.base)) { vali_service_conn_disconnect(vali_service_call_get_conn(call.base)); return; } // TODO: introduce a new function to close without a reply cert_Test11_close_with_reply(call, NULL); } static void handle_end(struct cert_End_service_call call, const struct cert_End_in *in) { struct service_state *state = service_state_from_call(call.base); if (!service_check_client_id(state, in->client_id)) { cert_error_ClientIdError_close_service_call(call.base, NULL); return; } bool all_ok = state->prev_test_num == 11; free(state->client_id); state->client_id = NULL; state->prev_test_num = 0; cert_End_close_with_reply(call, &(struct cert_End_out){ .all_ok = all_ok, }); } static const struct cert_handler handler = { .Start = handle_start, .Test01 = handle_test01, .Test02 = handle_test02, .Test03 = handle_test03, .Test04 = handle_test04, .Test05 = handle_test05, .Test06 = handle_test06, .Test07 = handle_test07, .Test08 = handle_test08, .Test09 = handle_test09, .Test10 = handle_test10, .Test11 = handle_test11, .End = handle_end, }; static struct vali_registry *service_register(struct vali_service *service, struct service_state *state) { const struct vali_registry_options registry_options = { .vendor = "emersion", .product = "Varlink Certification Service", .version = "1.0", .url = "https://gitlab.freedesktop.org/emersion/vali", }; struct vali_registry *registry = vali_registry_create(®istry_options); if (registry == NULL) { return NULL; } vali_registry_add(registry, &cert_interface, cert_get_call_handler(&handler)); vali_service_set_call_handler(service, vali_registry_get_call_handler(registry)); vali_service_set_user_data(service, state); return registry; } static bool service_run_once_with_fd(int socket_fd) { struct vali_service *service = vali_service_create(); if (service == NULL) { fprintf(stderr, "Failed to create service\n"); return false; } struct service_state state = {0}; struct vali_registry *registry = service_register(service, &state); if (registry == NULL) { fprintf(stderr, "Failed to create registry\n"); return false; } struct vali_service_conn *service_conn = vali_service_create_conn(service, socket_fd); if (service_conn == NULL) { fprintf(stderr, "Failed to create service connection\n"); return false; } while (1) { struct pollfd pollfds[] = { { .fd = vali_service_get_fd(service), .events = POLLIN, }, { .fd = socket_fd, .events = 0, }, }; if (poll(pollfds, sizeof(pollfds) / sizeof(pollfds[0]), -1) < 0) { perror("poll"); return false; } if (pollfds[0].revents & POLLIN) { if (!vali_service_dispatch(service)) { fprintf(stderr, "Failed to dispatch service\n"); return false; } } if (pollfds[1].revents & (POLLHUP | POLLERR)) { break; } } vali_service_destroy(service); vali_registry_destroy(registry); return true; } int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "%s", usage); return 1; } const char *subcmd = argv[1]; if (strcmp(subcmd, "client") == 0) { if (argc != 3) { fprintf(stderr, "%s", usage); return 1; } const char *path = argv[2]; struct vali_client *client = vali_client_connect_unix(path); if (client == NULL) { fprintf(stderr, "Failed to connect to %s\n", path); return 1; } if (!run_client(client)) { return 1; } } else if (strcmp(subcmd, "service") == 0) { if (argc != 3) { fprintf(stderr, "%s", usage); return 1; } const char *path = argv[2]; struct vali_service *service = vali_service_create(); if (service == NULL) { fprintf(stderr, "Failed to create service\n"); return 1; } struct service_state state = {0}; struct vali_registry *registry = service_register(service, &state); if (registry == NULL) { fprintf(stderr, "Failed to create registry\n"); return 1; } unlink(path); if (!vali_service_listen_unix(service, path)) { fprintf(stderr, "Failed to start service listener\n"); return 1; } fprintf(stderr, "Listening on %s\n", path); while (vali_service_dispatch(service)); fprintf(stderr, "Failed to dispatch service\n"); vali_service_destroy(service); vali_registry_destroy(registry); return 1; } else if (strcmp(subcmd, "self-test") == 0) { if (argc != 2) { fprintf(stderr, "%s", usage); return 1; } int sockets[2] = { -1, -1 }; int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); assert(ret == 0); pid_t pid = fork(); assert(pid >= 0); if (pid == 0) { close(sockets[1]); if (!service_run_once_with_fd(sockets[0])) { _exit(1); } _exit(0); } close(sockets[0]); struct vali_client *client = vali_client_connect_fd(sockets[1]); if (client == NULL) { fprintf(stderr, "Failed to create client\n"); return 1; } if (!run_client(client)) { return 1; } int stat = 0; if (waitpid(pid, &stat, 0) < 0) { perror("waitpid"); return 1; } else if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0) { fprintf(stderr, "Service process failed\n"); return 1; } } else { fprintf(stderr, "%s", usage); return 1; } return 0; } vali-0.1.0/test/meson.build000066400000000000000000000013131507023532100155600ustar00rootroot00000000000000test_gen = custom_target( 'test_gen', command: [tool, 'generate', '--prefix=test', '@INPUT@', '@OUTPUT0@', '@OUTPUT1@'], input: 'test.varlink', output: ['test-gen.h', 'test-gen.c'], ) test_basic = executable('basic', ['basic.c', test_gen], dependencies: vali) test('basic', test_basic) certification_gen = custom_target( 'certification_gen', command: [tool, 'generate', '--prefix=cert', '@INPUT@', '@OUTPUT0@', '@OUTPUT1@'], input: 'org.varlink.certification.varlink', output: ['certification-gen.h', 'certification-gen.c'], ) certification = executable( 'certification', ['certification.c', certification_gen], dependencies: [vali, jsonc], ) test('certification', certification, args: ['self-test']) vali-0.1.0/test/org.varlink.certification.varlink000066400000000000000000000043161507023532100220720ustar00rootroot00000000000000# Interface to test varlink implementations against. # First you write a varlink client calling: # Start, Test01, Test02, …, Test09, End # The return value of the previous call should be the argument of the next call. # Then you test this client against well known servers like python or rust from # https://github.com/varlink/ # # Next you write a varlink server providing the same service as the well known ones. # Now run your client against it and run well known clients like python or rust # from https://github.com/varlink/ against your server. If all works out, then # your new language bindings should be varlink certified. interface org.varlink.certification type Interface ( foo: ?[]?[string](foo, bar, baz), anon: (foo: bool, bar: bool) ) type MyType ( object: object, enum: (one, two, three), struct: (first: int, second: string), array: []string, dictionary: [string]string, stringset: [string](), nullable: ?string, nullable_array_struct: ?[](first: int, second: string), interface: Interface ) method Start() -> (client_id: string) method Test01(client_id: string) -> (bool: bool) method Test02(client_id: string, bool: bool) -> (int: int) method Test03(client_id: string, int: int) -> (float: float) method Test04(client_id: string, float: float) -> (string: string) method Test05(client_id: string, string: string) -> ( bool: bool, int: int, float: float, string: string ) method Test06( client_id: string, bool: bool, int: int, float: float, string: string ) -> ( struct: ( bool: bool, int: int, float: float, string: string ) ) method Test07( client_id: string, struct: ( bool: bool, int: int, float: float, string: string ) ) -> (map: [string]string) method Test08(client_id: string, map: [string]string) -> (set: [string]()) method Test09(client_id: string, set: [string]()) -> (mytype: MyType) # returns more than one reply with "continues" method Test10(client_id: string, mytype: MyType) -> (string: string) # must be called as "oneway" method Test11(client_id: string, last_more_replies: []string) -> () method End(client_id: string) -> (all_ok: bool) error ClientIdError () error CertificationError (wants: object, got: object) vali-0.1.0/test/test.varlink000066400000000000000000000004071507023532100157700ustar00rootroot00000000000000interface fr.emersion.vali.test type Enum (a, b, c) type Struct ( foo: int, bar: string, baz: ?Enum ) method Echo(s: Struct, a: []bool, m: [string]int) -> (s: Struct, a: []bool, m: [string]int) method CountUntil(n: int) -> (n: int) method Stop() -> () vali-0.1.0/tool.c000066400000000000000000001220661507023532100135710ustar00rootroot00000000000000#include #include #include #include #include "def.h" #include "util.h" #ifdef __GNUC__ #define ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) #else #define ATTRIB_PRINTF(start, end) #endif static struct varlink_interface *iface = NULL; static char *prefix = NULL; ATTRIB_PRINTF(1, 2) static char *format_str(const char *fmt, ...) { va_list args; va_start(args, fmt); int len = vsnprintf(NULL, 0, fmt, args); va_end(args); if (len < 0) { return NULL; } size_t size = (size_t)len + 1; char *str = malloc(size); if (str == NULL) { return NULL; } va_start(args, fmt); vsnprintf(str, size, fmt, args); va_end(args); return str; } static void print_indent(FILE *f, int level) { for (int i = 0; i < level; i++) { fprintf(f, " "); } } ATTRIB_PRINTF(3, 4) static void fprintf_indent(FILE *f, int level, const char *fmt, ...) { print_indent(f, level); va_list args; va_start(args, fmt); vfprintf(f, fmt, args); va_end(args); } static void print_iface_include_guard(FILE *f, const char *iface_name) { for (size_t i = 0; iface_name[i] != '\0'; i++) { char ch = iface_name[i]; if ('a' <= ch && ch <= 'z') { ch -= 'a' - 'A'; } if (ch == '.' || ch == '-') { fprintf(f, "_"); } else { fprintf(f, "%c", ch); } } fprintf(f, "_H"); } static void print_description(FILE *f, int indent, const char *description) { size_t len = strlen(description); if (len > 0 && description[len - 1] == '\n') { len--; } fprintf_indent(f, indent, "/**\n"); fprintf_indent(f, indent, " * "); for (size_t i = 0; i < len; i++) { char ch = description[i]; fputc(ch, f); if (ch == '\n') { fprintf_indent(f, indent, " * "); } } fprintf(f, "\n"); fprintf_indent(f, indent, " */\n"); } // See https://en.cppreference.com/w/c/keyword.html static const char *const reserved_keywords[] = { "alignas", "alignof", "auto", "bool", "break", "case", "char", "const", "constexpr", "continue", "default", "do", "double", "else", "enum", "extern", "false", "float", "for", "goto", "if", "inline", "int", "long", "nullptr", "register", "restrict", "return", "short", "signed", "sizeof", "static", "static_assert", "struct", "switch", "thread_local", "true", "typedef", "typeof", "typeof_unqual", "union", "unsigned", "void", "volatile", "while", }; #define RESERVED_KEYWORDS_LEN (sizeof(reserved_keywords) / sizeof(reserved_keywords[0])) #define ESCAPED_KEYWORD_CAP 128 static char escaped_keywords[ESCAPED_KEYWORD_CAP][RESERVED_KEYWORDS_LEN] = {0}; static const char *escape_keyword(const char *str) { for (size_t i = 0; i < RESERVED_KEYWORDS_LEN; i++) { if (strcmp(str, reserved_keywords[i]) == 0) { char *escaped = escaped_keywords[i]; snprintf(escaped, ESCAPED_KEYWORD_CAP, "%s_", str); return escaped; } } return str; } static void gen_type(FILE *f, int indent, const char *name, const struct varlink_type *t); static void gen_struct(FILE *f, int indent, const char *name, const struct varlink_struct *s) { fprintf(f, "struct %s_%s {\n", prefix, name); for (size_t i = 0; i < s->fields_len; i++) { const char *field_name = s->field_names[i]; const struct varlink_type *t = &s->field_types[i]; char *nested_name = format_str("%s_%s", name, field_name); print_indent(f, indent + 1); gen_type(f, indent + 1, nested_name, t); fprintf(f, "%s;\n", escape_keyword(field_name)); free(nested_name); } if (s->fields_len == 0) { print_indent(f, indent + 1); fprintf(f, "char _;\n"); } fprintf_indent(f, indent, "}"); } static void gen_enum(FILE *f, int indent, const char *name, const struct varlink_enum *e) { fprintf(f, "enum %s_%s {\n", prefix, name); for (size_t i = 0; i < e->entries_len; i++) { fprintf_indent(f, indent, " %s_%s_%s,\n", prefix, name, e->entries[i]); } fprintf_indent(f, indent, "}"); } static void gen_type_alias(FILE *f, const struct varlink_type_alias *type_alias) { if (type_alias->description != NULL) { print_description(f, 0, type_alias->description); } switch (type_alias->type.kind) { case VARLINK_STRUCT: gen_struct(f, 0, type_alias->name, &type_alias->type.struct_); break; case VARLINK_ENUM: gen_enum(f, 0, type_alias->name, &type_alias->type.enum_); break; default: abort(); // unreachable } fprintf(f, ";\n"); } static const struct varlink_type *find_type(const char *name) { for (size_t i = 0; i < iface->type_aliases_len; i++) { if (strcmp(iface->type_aliases[i].name, name) == 0) { return &iface->type_aliases[i].type; } } abort(); } static void gen_type(FILE *f, int indent, const char *name, const struct varlink_type *t) { char *nested_name; switch (t->kind) { case VARLINK_STRUCT: gen_struct(f, indent, name, &t->struct_); fprintf(f, " "); break; case VARLINK_ENUM: gen_enum(f, indent, name, &t->enum_); fprintf(f, " "); break; case VARLINK_NAME:; const struct varlink_type *ref_type = find_type(t->name); const char *kind; switch (ref_type->kind) { case VARLINK_STRUCT: kind = "struct"; break; case VARLINK_ENUM: kind = "enum"; break; default: abort(); } fprintf(f, "%s %s_%s ", kind, prefix, t->name); break; case VARLINK_BOOL: fprintf(f, "bool "); break; case VARLINK_INT: fprintf(f, "int "); break; case VARLINK_FLOAT: fprintf(f, "double "); break; case VARLINK_STRING: fprintf(f, "char *"); break; case VARLINK_ARRAY: nested_name = format_str("%s_entry", name); fprintf(f, "struct %s_%s {\n", prefix, name); print_indent(f, indent + 1); gen_type(f, indent + 1, nested_name, t->inner); fprintf(f, "*data;\n"); fprintf_indent(f, indent, " size_t len;\n"); fprintf_indent(f, indent, "} "); free(nested_name); break; case VARLINK_MAP: nested_name = format_str("%s_value", name); fprintf(f, "struct %s_%s {\n", prefix, name); fprintf_indent(f, indent, " struct %s_%s_entry {\n", prefix, name); fprintf_indent(f, indent, " char *key;\n"); print_indent(f, indent + 2); gen_type(f, indent + 2, nested_name, t->inner); fprintf(f, "value;\n"); fprintf_indent(f, indent, " } *data;\n"); fprintf_indent(f, indent, " size_t len;\n"); fprintf_indent(f, indent, "} "); free(nested_name); break; case VARLINK_OBJECT: fprintf(f, "struct json_object *"); break; } if (t->nullable && t->kind != VARLINK_STRING) { fprintf(f, "*"); } } static void gen_type_ref(FILE *f, const char *name, const struct varlink_type *t) { switch (t->kind) { case VARLINK_STRUCT: case VARLINK_ENUM: case VARLINK_ARRAY: case VARLINK_MAP: fprintf(f, "%s %s_%s %s", t->kind == VARLINK_ENUM ? "enum" : "struct", prefix, name, t->nullable ? "*" : ""); break; default: gen_type(f, 0, name, t); break; } } static void gen_enum_encoder_code(FILE *f, int indent, const char *name, const char *out, const char *val, const struct varlink_enum *e) { fprintf_indent(f, indent, "switch (%s) {\n", val); for (size_t i = 0; i < e->entries_len; i++) { const char *entry = e->entries[i]; fprintf_indent(f, indent, "case %s_%s_%s:\n", prefix, name, entry); fprintf_indent(f, indent, " %s = json_object_new_string(\"%s\");\n", out, entry); fprintf_indent(f, indent, " break;\n"); } fprintf_indent(f, indent, "}\n"); } static void gen_struct_encoder_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s); static void gen_type_encoder_code(FILE *f, int indent, const char *name, const char *out_expr, const char *in_expr, const struct varlink_type *t) { fprintf_indent(f, indent, "struct json_object *%s = ", out_expr); if (t->nullable) { fprintf(f, "NULL;\n"); fprintf_indent(f, indent, "if (%s != NULL) {\n", in_expr); indent++; fprintf_indent(f, indent, "%s = ", out_expr); } char *val = format_str("%s%s", t->nullable && t->kind != VARLINK_STRING ? "*" : "", in_expr); char *nested_accessor; if (t->nullable) { nested_accessor = format_str("%s->", in_expr); } else { nested_accessor = format_str("%s.", in_expr); } char *nested_name; switch (t->kind) { case VARLINK_STRUCT: fprintf(f, "json_object_new_object();\n"); if (t->struct_.fields_len == 0) { break; } fprintf_indent(f, indent, "{\n"); fprintf_indent(f, indent, " struct json_object *raw = %s;\n", out_expr); gen_struct_encoder_code(f, indent + 1, name, nested_accessor, &t->struct_); fprintf_indent(f, indent, "}\n"); break; case VARLINK_ENUM: fprintf(f, "NULL;\n"); gen_enum_encoder_code(f, indent, name, out_expr, val, &t->enum_); break; case VARLINK_NAME:; const struct varlink_type *ref_type = find_type(t->name); const char *prefix = ""; if (t->nullable && ref_type->kind == VARLINK_ENUM) { prefix = "*"; } if (!t->nullable && ref_type->kind == VARLINK_STRUCT) { prefix = "&"; } fprintf(f, "%s_encode(%s%s);\n", t->name, prefix, in_expr); break; case VARLINK_BOOL: fprintf(f, "json_object_new_boolean(%s);\n", val); break; case VARLINK_INT: fprintf(f, "json_object_new_int(%s);\n", val); break; case VARLINK_FLOAT: fprintf(f, "json_object_new_double(%s);\n", val); break; case VARLINK_STRING: fprintf(f, "json_object_new_string(%s);\n", val); break; case VARLINK_ARRAY: fprintf(f, "json_object_new_array();\n"); fprintf_indent(f, indent, "for (size_t i = 0; i < %slen; i++) {\n", nested_accessor); nested_name = format_str("%s_entry", name); print_indent(f, indent + 1); gen_type_ref(f, nested_name, t->inner); fprintf(f, "item = %sdata[i];\n", nested_accessor); gen_type_encoder_code(f, indent + 1, nested_name, "raw_item", "item", t->inner); free(nested_name); fprintf_indent(f, indent, " json_object_array_add(%s, raw_item);\n", out_expr); fprintf_indent(f, indent, "}\n"); break; case VARLINK_MAP: fprintf(f, "json_object_new_object();\n"); fprintf_indent(f, indent, "for (size_t i = 0; i < %slen; i++) {\n", nested_accessor); nested_name = format_str("%s_value", name); fprintf_indent(f, indent, " MAYBE_UNUSED "); gen_type_ref(f, nested_name, t->inner); fprintf(f, "value = %sdata[i].value;\n", nested_accessor); gen_type_encoder_code(f, indent + 1, nested_name, "raw_value", "value", t->inner); free(nested_name); fprintf_indent(f, indent, " json_object_object_add(%s, %sdata[i].key, raw_value);\n", out_expr, nested_accessor); fprintf_indent(f, indent, "}\n"); break; case VARLINK_OBJECT: fprintf(f, "json_object_get(%s);\n", val); break; } free(val); free(nested_accessor); if (t->nullable) { indent--; fprintf_indent(f, indent, "}\n"); } } static void gen_struct_encoder_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s) { for (size_t i = 0; i < s->fields_len; i++) { const char *field_name = s->field_names[i]; const struct varlink_type *t = &s->field_types[i]; char *nested_name = format_str("%s_%s", name, field_name); char *in_expr = format_str("%s%s", accessor, escape_keyword(field_name)); char *out_expr = format_str("raw_%s", field_name); gen_type_encoder_code(f, indent, nested_name, out_expr, in_expr, t); fprintf_indent(f, indent, "json_object_object_add(raw, \"%s\", %s);\n", field_name, out_expr); free(nested_name); free(in_expr); free(out_expr); } } static void gen_struct_encoder(FILE *f, const char *name, const struct varlink_struct *s) { fprintf(f, "MAYBE_UNUSED\n"); fprintf(f, "static struct json_object *%s_encode(const struct %s_%s *v) {\n", name, prefix, name); fprintf(f, " struct json_object *raw = json_object_new_object();\n"); gen_struct_encoder_code(f, 1, name, "v->", s); fprintf(f, " return raw;\n"); fprintf(f, "}\n"); } static void gen_enum_encoder(FILE *f, const char *name, const struct varlink_enum *e) { fprintf(f, "MAYBE_UNUSED\n"); fprintf(f, "static struct json_object *%s_encode(enum %s_%s v) {\n", name, prefix, name); fprintf(f, " struct json_object *raw = NULL;\n"); gen_enum_encoder_code(f, 1, name, "raw", "v", e); fprintf(f, " return raw;\n"); fprintf(f, "}\n"); } static void gen_type_alias_encoder(FILE *f, const struct varlink_type_alias *type_alias) { switch (type_alias->type.kind) { case VARLINK_STRUCT: gen_struct_encoder(f, type_alias->name, &type_alias->type.struct_); break; case VARLINK_ENUM: gen_enum_encoder(f, type_alias->name, &type_alias->type.enum_); break; default: abort(); // unreachable } } static void gen_enum_decoder_code(FILE *f, int indent, const char *name, const char *val, const struct varlink_enum *e) { for (size_t i = 0; i < e->entries_len; i++) { const char *entry = e->entries[i]; if (i == 0) { print_indent(f, indent); } fprintf(f, "if (strcmp(raw, \"%s\") == 0) {\n", entry); fprintf_indent(f, indent, " %s = %s_%s_%s;\n", val, prefix, name, entry); fprintf_indent(f, indent, "}"); if (i == e->entries_len - 1) { fprintf(f, "\n"); } else { fprintf(f, " else "); } } // TODO: should an unknown value be an error? } static const char *json_type_from_varlink_kind(enum varlink_kind kind) { switch (kind) { case VARLINK_STRUCT: return "json_type_object"; case VARLINK_ENUM: return "json_type_string"; case VARLINK_NAME: return NULL; // too early to tell case VARLINK_BOOL: return "json_type_boolean"; case VARLINK_INT: return "json_type_int"; case VARLINK_FLOAT: return "json_type_double"; case VARLINK_STRING: return "json_type_string"; case VARLINK_OBJECT: return NULL; // can be anything case VARLINK_ARRAY: return "json_type_array"; case VARLINK_MAP: return "json_type_object"; } abort(); // unreachable } static void gen_struct_decoder_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s); static void gen_type_decoder_code(FILE *f, int indent, const char *name, const char *out_expr, const char *obj_expr, const struct varlink_type *t) { if (t->nullable) { fprintf_indent(f, indent, "if (%s != NULL) {\n", obj_expr); indent++; } else { fprintf_indent(f, indent, "if (%s == NULL) {\n", obj_expr); fprintf_indent(f, indent, " return false;\n"); fprintf_indent(f, indent, "}\n"); } const char *json_type = json_type_from_varlink_kind(t->kind); if (json_type != NULL) { fprintf_indent(f, indent, "if (json_object_get_type(%s) != %s) {\n", obj_expr, json_type); fprintf_indent(f, indent, " return false;\n"); fprintf_indent(f, indent, "}\n"); } if (t->nullable && t->kind != VARLINK_STRING) { fprintf_indent(f, indent, "%s = calloc(1, sizeof(*%s));\n", out_expr, out_expr); } char *val = format_str("%s%s", t->nullable && t->kind != VARLINK_STRING ? "*" : "", out_expr); char *nested_accessor; if (t->nullable) { nested_accessor = format_str("%s->", out_expr); } else { nested_accessor = format_str("%s.", out_expr); } char *nested_name; switch (t->kind) { case VARLINK_STRUCT: if (t->struct_.fields_len == 0) { break; } fprintf_indent(f, indent, "{\n"); fprintf_indent(f, indent, " struct json_object *obj = %s;\n", obj_expr); fprintf(f, "\n"); gen_struct_decoder_code(f, indent + 1, name, nested_accessor, &t->struct_); fprintf_indent(f, indent, "}\n"); break; case VARLINK_ENUM: fprintf_indent(f, indent, "const char *raw = json_object_get_string(%s);\n", obj_expr); gen_enum_decoder_code(f, indent, name, val, &t->enum_); break; case VARLINK_NAME: fprintf_indent(f, indent, "if (!%s_decode(%s%s, %s)) {\n", t->name, t->nullable ? "" : "&", out_expr, obj_expr); fprintf_indent(f, indent, " return false;\n"); fprintf_indent(f, indent, "}\n"); break; case VARLINK_BOOL: fprintf_indent(f, indent, "%s = json_object_get_boolean(%s);\n", val, obj_expr); break; case VARLINK_INT: fprintf_indent(f, indent, "%s = (int)json_object_get_int64(%s);\n", val, obj_expr); break; case VARLINK_FLOAT: fprintf_indent(f, indent, "%s = json_object_get_double(%s);\n", val, obj_expr); break; case VARLINK_STRING: fprintf_indent(f, indent, "%s = strdup(json_object_get_string(%s));\n", val, obj_expr); break; case VARLINK_ARRAY: fprintf_indent(f, indent, "size_t %s_len = json_object_array_length(%s);\n", obj_expr, obj_expr); fprintf_indent(f, indent, "%sdata = calloc(%s_len, sizeof(%sdata[0]));\n", nested_accessor, obj_expr, nested_accessor); fprintf_indent(f, indent, "if (%sdata == NULL) {\n", nested_accessor); fprintf_indent(f, indent, " return false;\n"); fprintf_indent(f, indent, "}\n"); fprintf_indent(f, indent, "for (size_t i = 0; i < %s_len; i++) {\n", obj_expr); fprintf_indent(f, indent, " struct json_object *item_obj = json_object_array_get_idx(%s, i);\n", obj_expr); nested_name = format_str("%s_entry", name); print_indent(f, indent + 1); gen_type_ref(f, nested_name, t->inner); fprintf(f, "item = {0};\n"); gen_type_decoder_code(f, indent + 1, nested_name, "item", "item_obj", t->inner); fprintf_indent(f, indent, " %sdata[i] = item;\n", nested_accessor); free(nested_name); fprintf_indent(f, indent, " %slen++;\n", nested_accessor); fprintf_indent(f, indent, "}\n"); break; case VARLINK_MAP: fprintf_indent(f, indent, "size_t %s_len = (size_t)json_object_object_length(%s);\n", obj_expr, obj_expr); fprintf_indent(f, indent, "%sdata = calloc(%s_len, sizeof(%sdata[0]));\n", nested_accessor, obj_expr, nested_accessor); fprintf_indent(f, indent, "if (%sdata == NULL) {\n", nested_accessor); fprintf_indent(f, indent, " return false;\n"); fprintf_indent(f, indent, "}\n"); fprintf_indent(f, indent, "struct json_object_iter %s_iter;\n", obj_expr); fprintf_indent(f, indent, "json_object_object_foreachC(%s, %s_iter) {\n", obj_expr, obj_expr); fprintf_indent(f, indent, " char *key = strdup(%s_iter.key);\n", obj_expr); fprintf_indent(f, indent, " if (key == NULL) {\n"); fprintf_indent(f, indent, " return false;\n"); fprintf_indent(f, indent, " }\n"); fprintf_indent(f, indent, " %sdata[%slen].key = key;\n", nested_accessor, nested_accessor); fprintf(f, "\n"); char *nested_obj_expr = format_str("%s_iter.val", obj_expr); nested_name = format_str("%s_value", name); print_indent(f, indent + 1); gen_type_ref(f, nested_name, t->inner); fprintf(f, "value = {0};\n"); gen_type_decoder_code(f, indent + 1, nested_name, "value", nested_obj_expr, t->inner); fprintf_indent(f, indent, " %sdata[%slen].value = value;\n", nested_accessor, nested_accessor); free(nested_obj_expr); free(nested_name); fprintf_indent(f, indent, " %slen++;\n", nested_accessor); fprintf_indent(f, indent, "}\n"); break; case VARLINK_OBJECT: fprintf_indent(f, indent, "%s = json_object_get(%s);\n", val, obj_expr); break; } free(val); free(nested_accessor); if (t->nullable) { indent--; fprintf_indent(f, indent, "}\n"); } } static void gen_struct_decoder_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s) { for (size_t i = 0; i < s->fields_len; i++) { const char *field_name = s->field_names[i]; const struct varlink_type *t = &s->field_types[i]; char *nested_name = format_str("%s_%s", name, field_name); char *out_expr = format_str("%s%s", accessor, escape_keyword(field_name)); char *in_expr = format_str("raw_%s", field_name); if (i > 0) { fprintf(f, "\n"); } fprintf_indent(f, indent, "struct json_object *raw_%s = json_object_object_get(obj, \"%s\");\n", field_name, field_name); gen_type_decoder_code(f, indent, nested_name, out_expr, in_expr, t); free(nested_name); free(out_expr); free(in_expr); } } static void gen_struct_decoder(FILE *f, const char *name, const struct varlink_struct *s) { // Note: we treat null like an empty object because the "parameters" field // in method calls, replies and errors require it fprintf(f, "MAYBE_UNUSED\n"); fprintf(f, "static bool %s_decode(struct %s_%s *out, struct json_object *obj) {\n", name, prefix, name); fprintf(f, " if (json_object_get_type(obj) != json_type_object && obj != NULL) {\n" " return false;\n" " }\n" "\n"); gen_struct_decoder_code(f, 1, name, "out->", s); fprintf(f, "\n"); fprintf(f, " return true;\n"); fprintf(f, "}\n"); } static void gen_enum_decoder(FILE *f, const char *name, const struct varlink_enum *e) { fprintf(f, "MAYBE_UNUSED\n"); fprintf(f, "static bool %s_decode(enum %s_%s *out, struct json_object *obj) {\n", name, prefix, name); fprintf(f, " if (json_object_get_type(obj) != json_type_string) {\n" " return false;\n" " }\n" " const char *raw = json_object_get_string(obj);\n"); gen_enum_decoder_code(f, 1, name, "*out", e); fprintf(f, " return true;\n"); fprintf(f, "}\n"); } static void gen_type_alias_decoder(FILE *f, const struct varlink_type_alias *type_alias) { switch (type_alias->type.kind) { case VARLINK_STRUCT: gen_struct_decoder(f, type_alias->name, &type_alias->type.struct_); break; case VARLINK_ENUM: gen_enum_decoder(f, type_alias->name, &type_alias->type.enum_); break; default: abort(); // unreachable } } static void gen_struct_finisher_prototype(FILE *f, const char *name) { fprintf(f, "void %s_%s_finish(struct %s_%s *v)", prefix, name, prefix, name); } static void gen_struct_finisher_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s); static void gen_type_finisher_code(FILE *f, int indent, const char *name, const char *expr, const struct varlink_type *t) { if (t->nullable) { fprintf_indent(f, indent, "if (%s != NULL) {\n", expr); indent++; } char *nested_accessor; if (t->nullable) { nested_accessor = format_str("%s->", expr); } else { nested_accessor = format_str("%s.", expr); } char *nested_name; switch (t->kind) { case VARLINK_STRUCT:; gen_struct_finisher_code(f, indent, name, nested_accessor, &t->struct_); break; case VARLINK_NAME:; const struct varlink_type *ref_type = find_type(t->name); if (ref_type->kind == VARLINK_STRUCT) { fprintf_indent(f, indent, "%s_%s_finish(%s%s);\n", prefix, t->name, t->nullable ? "" : "&", expr); } break; case VARLINK_ENUM: case VARLINK_BOOL: case VARLINK_INT: case VARLINK_FLOAT: break; // nothing to do case VARLINK_STRING: break; // handled below case VARLINK_ARRAY: fprintf_indent(f, indent, "for (size_t i = 0; i < %slen; i++) {\n", nested_accessor); nested_name = format_str("%s_entry", name); fprintf_indent(f, indent, " MAYBE_UNUSED "); gen_type_ref(f, nested_name, t->inner); fprintf(f, "entry = %sdata[i];\n", nested_accessor); gen_type_finisher_code(f, indent + 1, nested_name, "entry", t->inner); free(nested_name); fprintf_indent(f, indent, "}\n"); fprintf_indent(f, indent, "free(%sdata);\n", nested_accessor); break; case VARLINK_MAP: fprintf_indent(f, indent, "for (size_t i = 0; i < %slen; i++) {\n", nested_accessor); fprintf_indent(f, indent, " free(%sdata[i].key);\n", nested_accessor); nested_name = format_str("%s_value", name); fprintf_indent(f, indent, " MAYBE_UNUSED "); gen_type_ref(f, nested_name, t->inner); fprintf(f, "value = %sdata[i].value;\n", nested_accessor); gen_type_finisher_code(f, indent + 1, nested_name, "value", t->inner); free(nested_name); fprintf_indent(f, indent, "}\n"); fprintf_indent(f, indent, "free(%sdata);\n", nested_accessor); break; case VARLINK_OBJECT: fprintf_indent(f, indent, "json_object_put(%s);\n", expr); break; } free(nested_accessor); if (t->nullable || t->kind == VARLINK_STRING) { fprintf_indent(f, indent, "free(%s);\n", expr); } if (t->nullable) { indent--; fprintf_indent(f, indent, "}\n"); } } static void gen_struct_finisher_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s) { for (size_t i = 0; i < s->fields_len; i++) { const char *field_name = s->field_names[i]; const struct varlink_type *t = &s->field_types[i]; char *nested_name = format_str("%s_%s", name, field_name); char *expr = format_str("%s%s", accessor, escape_keyword(field_name)); gen_type_finisher_code(f, indent, nested_name, expr, t); free(nested_name); free(expr); } } static void gen_struct_finisher(FILE *f, const char *name, const struct varlink_struct *s) { gen_struct_finisher_prototype(f, name); fprintf(f, " {\n"); gen_struct_finisher_code(f, 1, name, "v->", s); fprintf(f, "}\n"); } static void gen_error_header(FILE *f, const struct varlink_error *error) { const char *name = error->name; const struct varlink_struct *s = &error->struct_; if (error->description != NULL) { print_description(f, 0, error->description); } char *id = format_str("error_%s", name); gen_struct(f, 0, id, s); fprintf(f, ";\n"); free(id); fprintf(f, "/** Try to convert a struct vali_error into a %s error. */\n", name); fprintf(f, "bool %s_error_%s_from(struct %s_error_%s *out, const struct vali_error *err);\n", prefix, name, prefix, name); fprintf(f, "/** Close a service call with a %s error. */\n", name); fprintf(f, "void %s_error_%s_close_service_call(struct vali_service_call *call, const struct %s_error_%s *params);\n", prefix, name, prefix, name); } static void gen_method_call_prototype(FILE *f, const struct varlink_method *method) { fprintf(f, "bool %s_%s(struct vali_client *c, const struct %s_%s_in *in, struct %s_%s_out *out, struct vali_error *err)", prefix, method->name, prefix, method->name, prefix, method->name); } static void gen_method_call_more_prototype(FILE *f, const struct varlink_method *method) { fprintf(f, "struct %s_%s_client_call %s_%s_more(struct vali_client *c, const struct %s_%s_in *in)", prefix, method->name, prefix, method->name, prefix, method->name); } static void gen_method_call_oneway_prototype(FILE *f, const struct varlink_method *method) { fprintf(f, "bool %s_%s_oneway(struct vali_client *c, const struct %s_%s_in *in)", prefix, method->name, prefix, method->name); } static void gen_method_header(FILE *f, const struct varlink_method *method) { char *name = format_str("%s_in", method->name); fprintf(f, "/** Input for %s calls. */\n", method->name), gen_struct(f, 0, name, &method->in); fprintf(f, ";\n"); free(name); name = format_str("%s_out", method->name); fprintf(f, "/** Output for %s calls. */\n", method->name), gen_struct(f, 0, name, &method->out); fprintf(f, ";\n"); fprintf(f, "/** Release resources held by %s call output. */\n", method->name), gen_struct_finisher_prototype(f, name); fprintf(f, ";\n"); free(name); if (method->description != NULL) { print_description(f, 0, method->description); } else { fprintf(f, "/** Perform a %s client call. */\n", method->name); } gen_method_call_prototype(f, method); fprintf(f, ";\n"); fprintf(f, "/** A %s call for a client. */\n", method->name); fprintf(f, "struct %s_%s_client_call {\n", prefix, method->name); fprintf(f, " struct vali_client_call *base;\n"); fprintf(f, "};\n"); fprintf(f, "/** Send a request for a %s client call, indicating that multiple replies are expected. */\n", method->name); gen_method_call_more_prototype(f, method); fprintf(f, ";\n"); fprintf(f, "/** Wait for a %s reply. */\n", method->name); fprintf(f, "bool %s_%s_client_call_wait(struct %s_%s_client_call call, struct %s_%s_out *out, struct vali_error *err);\n", prefix, method->name, prefix, method->name, prefix, method->name); fprintf(f, "/** Send a request for a %s client call, indicating that no reply is expected. */", method->name); gen_method_call_oneway_prototype(f, method); fprintf(f, ";\n"); fprintf(f, "/** A %s call for a service. */\n", method->name); fprintf(f, "struct %s_%s_service_call {\n", prefix, method->name); fprintf(f, " struct vali_service_call *base;\n"); fprintf(f, "};\n"); fprintf(f, "/** Close a %s service call with a final reply. */\n", method->name); fprintf(f, "void %s_%s_close_with_reply(struct %s_%s_service_call call, const struct %s_%s_out *params);\n", prefix, method->name, prefix, method->name, prefix, method->name); fprintf(f, "/** Send a reply for a %s service call, indicating that more replies will be sent. */\n", method->name); fprintf(f, "void %s_%s_reply(struct %s_%s_service_call call, const struct %s_%s_out *params);\n", prefix, method->name, prefix, method->name, prefix, method->name); } static void gen_header(FILE *f, const struct varlink_interface *iface) { fprintf(f, "#ifndef "); print_iface_include_guard(f, iface->name); fprintf(f, "\n"); fprintf(f, "#define "); print_iface_include_guard(f, iface->name); fprintf(f, "\n\n"); fprintf(f, "#include \n" "#include \n" "\n" "struct json_object;\n" "struct vali_client;\n" "struct vali_error;\n" "struct vali_service_call;\n" "struct vali_registry;\n" "\n"); if (iface->description != NULL) { print_description(f, 0, iface->description); fprintf(f, "\n"); } for (size_t i = 0; i < iface->type_aliases_len; i++) { gen_type_alias(f, &iface->type_aliases[i]); fprintf(f, "\n"); } for (size_t i = 0; i < iface->errors_len; i++) { gen_error_header(f, &iface->errors[i]); fprintf(f, "\n"); } for (size_t i = 0; i < iface->methods_len; i++) { gen_method_header(f, &iface->methods[i]); fprintf(f, "\n"); } fprintf(f, "/** Handler for %s. */\n", iface->name), fprintf(f, "struct %s_handler {\n", prefix); for (size_t i = 0; i < iface->methods_len; i++) { const struct varlink_method *method = &iface->methods[i]; if (method->description != NULL) { print_description(f, 1, method->description); } fprintf(f, " void (*%s)(struct %s_%s_service_call call, const struct %s_%s_in *in);\n", method->name, prefix, method->name, prefix, method->name); } fprintf(f, "};\n\n"); fprintf(f, "/** Get a service call handler from a %s handler. */\n", iface->name); fprintf(f, "struct vali_service_call_handler %s_get_call_handler(const struct %s_handler *handler);\n\n", prefix, prefix); fprintf(f, "/** Description of the %s interface. */\n", iface->name); fprintf(f, "extern const struct vali_registry_interface %s_interface;\n\n", prefix); fprintf(f, "#endif\n"); } static void gen_error_code(FILE *f, const struct varlink_error *error) { const char *name = error->name; const struct varlink_struct *s = &error->struct_; char *id = format_str("error_%s", name); gen_struct_encoder(f, id, s); gen_struct_decoder(f, id, s); free(id); fprintf(f, "bool %s_error_%s_from(struct %s_error_%s *out, const struct vali_error *err) {\n", prefix, name, prefix, name); fprintf(f, " if (err->name == NULL || strcmp(err->name, \"%s.%s\") != 0) {\n", iface->name, name); fprintf(f, " return false;\n"); fprintf(f, " }\n"); fprintf(f, " return error_%s_decode(out, err->parameters);\n", name); fprintf(f, "}\n"); fprintf(f, "void %s_error_%s_close_service_call(struct vali_service_call *call, const struct %s_error_%s *params) {\n", prefix, name, prefix, name); fprintf(f, " struct json_object *obj = error_%s_encode(params);\n", name); fprintf(f, " vali_service_call_close_with_error(call, \"%s.%s\", obj);\n", iface->name, name); fprintf(f, "}\n"); } static void gen_method_in_out_code(FILE *f, const char *method, const char *type, const struct varlink_struct *s) { char *name = format_str("%s_%s", method, type); gen_struct_encoder(f, name, s); fprintf(f, "\n"); gen_struct_decoder(f, name, s); fprintf(f, "\n"); if (strcmp(type, "in") == 0) { fprintf(f, "MAYBE_UNUSED\n" "static "); } else { fprintf(f, "HIDDEN\n"); } gen_struct_finisher(f, name, s); fprintf(f, "\n"); free(name); } static void gen_method_code(FILE *f, const struct varlink_method *method) { gen_method_in_out_code(f, method->name, "in", &method->in); gen_method_in_out_code(f, method->name, "out", &method->out); fprintf(f, "HIDDEN\n"); gen_method_call_prototype(f, method); fprintf(f, " {\n"); fprintf(f, " struct json_object *raw_in = %s_in_encode(in);\n", method->name); fprintf(f, " struct json_object *raw_out = NULL;\n"); fprintf(f, " if (!vali_client_call(c, \"%s.%s\", raw_in, &raw_out, err)) {\n", iface->name, method->name); fprintf(f, " return false;\n"); fprintf(f, " }\n"); fprintf(f, " bool ok = true;\n"); fprintf(f, " if (out != NULL) {\n"); fprintf(f, " *out = (struct %s_%s_out){0};\n", prefix, method->name); fprintf(f, " ok = %s_out_decode(out, raw_out);\n", method->name); fprintf(f, " }\n"); fprintf(f, " json_object_put(raw_out);\n"); fprintf(f, " return ok;\n"); fprintf(f, "}\n"); fprintf(f, "\n"); fprintf(f, "HIDDEN\n"); gen_method_call_more_prototype(f, method); fprintf(f, " {\n"); fprintf(f, " struct json_object *raw_in = %s_in_encode(in);\n", method->name); fprintf(f, " struct vali_client_call *call = vali_client_call_more(c, \"%s.%s\", raw_in);\n", iface->name, method->name); fprintf(f, " return (struct %s_%s_client_call){ .base = call };\n", prefix, method->name); fprintf(f, "}\n"); fprintf(f, "\n"); fprintf(f, "HIDDEN\n"); gen_method_call_oneway_prototype(f, method); fprintf(f, " {\n"); fprintf(f, " struct json_object *raw_in = %s_in_encode(in);\n", method->name); fprintf(f, " return vali_client_call_oneway(c, \"%s.%s\", raw_in);\n", iface->name, method->name); fprintf(f, "}\n"); fprintf(f, "\n"); fprintf(f, "HIDDEN\n"); fprintf(f, "bool %s_%s_client_call_wait(struct %s_%s_client_call call, struct %s_%s_out *out, struct vali_error *err) {\n", prefix, method->name, prefix, method->name, prefix, method->name); fprintf(f, " struct json_object *raw_out = NULL;\n"); fprintf(f, " if (!vali_client_call_wait(call.base, &raw_out, err)) {\n"); fprintf(f, " return false;\n"); fprintf(f, " }\n"); fprintf(f, " bool ok = true;\n"); fprintf(f, " if (out != NULL) {\n"); fprintf(f, " *out = (struct %s_%s_out){0};\n", prefix, method->name); fprintf(f, " ok = %s_out_decode(out, raw_out);\n", method->name); fprintf(f, " }\n"); fprintf(f, " json_object_put(raw_out);\n"); fprintf(f, " return ok;\n"); fprintf(f, "}\n"); fprintf(f, "HIDDEN\n"); fprintf(f, "void %s_%s_close_with_reply(struct %s_%s_service_call call, const struct %s_%s_out *params) {\n", prefix, method->name, prefix, method->name, prefix, method->name); fprintf(f, " struct json_object *raw = %s_out_encode(params);\n", method->name); fprintf(f, " vali_service_call_close_with_reply(call.base, raw);\n"); fprintf(f, "}\n"); fprintf(f, "HIDDEN\n"); fprintf(f, "void %s_%s_reply(struct %s_%s_service_call call, const struct %s_%s_out *params) {\n", prefix, method->name, prefix, method->name, prefix, method->name); fprintf(f, " struct json_object *raw = %s_out_encode(params);\n", method->name); fprintf(f, " vali_service_call_reply(call.base, raw);\n"); fprintf(f, "}\n"); } static void gen_code(FILE *f, const struct varlink_interface *iface, const char *header_basename, const char *def) { fprintf(f, "#include \n" "#include \n" "\n" "#include \n" "#include \n" "#include \n" "\n" "#include \"%s\"\n" "\n", header_basename); fprintf(f, "#if __STDC_VERSION__ >= 202311L\n" "#define MAYBE_UNUSED [[maybe_unused]]\n" "#elif defined(__GNUC__)\n" "#define MAYBE_UNUSED __attribute__((__unused__))\n" "#else\n" "#define MAYBE_UNUSED\n" "#endif\n\n"); fprintf(f, "#define HIDDEN\n" "#if defined(__has_attribute)\n" "#if __has_attribute(visibility)\n" "#undef HIDDEN\n" "#define HIDDEN __attribute__((visibility(\"hidden\")))\n" "#endif\n" "#endif\n\n"); for (size_t i = 0; i < iface->type_aliases_len; i++) { const struct varlink_type_alias *type_alias = &iface->type_aliases[i]; gen_type_alias_encoder(f, type_alias); fprintf(f, "\n"); gen_type_alias_decoder(f, type_alias); fprintf(f, "\n"); if (type_alias->type.kind == VARLINK_STRUCT) { fprintf(f, "MAYBE_UNUSED\n" "static "); gen_struct_finisher(f, type_alias->name, &type_alias->type.struct_); fprintf(f, "\n"); } } for (size_t i = 0; i < iface->methods_len; i++) { gen_method_code(f, &iface->methods[i]); fprintf(f, "\n"); } for (size_t i = 0; i < iface->errors_len; i++) { gen_error_code(f, &iface->errors[i]); fprintf(f, "\n"); } fprintf(f, "MAYBE_UNUSED\n" "static void fail_method_not_implemented(struct vali_service_call *call, const char *method) {\n" " struct json_object *params = json_object_new_object();\n" " json_object_object_add(params, \"method\", json_object_new_string(method));\n" " vali_service_call_close_with_error(call, \"org.varlink.service.MethodNotImplemented\", params);\n" "}\n" "\n" "MAYBE_UNUSED\n" "static void fail_invalid_parameter(struct vali_service_call *call, const char *param) {\n" " struct json_object *params = json_object_new_object();\n" " json_object_object_add(params, \"parameter\", json_object_new_string(param));\n" " vali_service_call_close_with_error(call, \"org.varlink.service.InvalidParameter\", params);\n" "}\n" "\n"); fprintf(f, "static void handle_call(struct vali_service_call *call, void *user_data) {\n" " const struct %s_handler *handler = user_data;\n" " const char *method = vali_service_call_get_method(call);\n" " struct json_object *raw_in = vali_service_call_get_parameters(call);\n", prefix); for (size_t i = 0; i < iface->methods_len; i++) { const struct varlink_method *method = &iface->methods[i]; fprintf(f, " if (strcmp(method, \"%s.%s\") == 0) {\n", iface->name, method->name); fprintf(f, " if (handler->%s == NULL) {\n", method->name); fprintf(f, " fail_method_not_implemented(call, method);\n"); fprintf(f, " return;\n"); fprintf(f, " }\n"); fprintf(f, " struct %s_%s_in in = {0};\n", prefix, method->name); fprintf(f, " if (!%s_in_decode(&in, raw_in)) {\n", method->name); // TODO: set parameter name fprintf(f, " fail_invalid_parameter(call, NULL);\n"); fprintf(f, " return;\n"); fprintf(f, " }\n"); fprintf(f, " handler->%s((struct %s_%s_service_call){call}, &in);\n", method->name, prefix, method->name); fprintf(f, " %s_%s_in_finish(&in);\n", prefix, method->name); fprintf(f, " return;\n"); fprintf(f, " }\n"); } fprintf(f, " struct json_object *params = json_object_new_object();\n" " json_object_object_add(params, \"method\", json_object_new_string(method));\n" " vali_service_call_close_with_error(call, \"org.varlink.service.MethodNotFound\", params);\n" "}\n" "\n"); fprintf(f, "HIDDEN\n" "const struct vali_registry_interface %s_interface = {\n" " .name = \"%s\",\n" " .definition =\n" " \"", prefix, iface->name); for (size_t i = 0; def[i] != '\0'; i++) { switch (def[i]) { case '\n': fprintf(f, "\\n\"\n" " \""); continue; case '\\': case '"': fputc('\\', f); break; } fputc(def[i], f); } fprintf(f, "\",\n"); fprintf(f, "};\n\n"); fprintf(f, "HIDDEN\n" "struct vali_service_call_handler %s_get_call_handler(const struct %s_handler *handler) {\n" " return (struct vali_service_call_handler){\n" " .func = handle_call,\n" " .user_data = (void *)handler,\n" " };\n" "}\n" "\n", prefix, prefix); } static char *read_full(FILE *f) { char buf[1024]; struct array arr = {0}; while (1) { size_t n = fread(buf, 1, sizeof(buf), f); if (n == 0) { if (feof(f)) { break; } else { goto err; } } char *dst = array_add(&arr, n); if (dst == NULL) { goto err; } memcpy(dst, buf, n); } char *end = array_add(&arr, 1); if (end == NULL) { goto err; } *end = '\0'; return arr.data; err: array_finish(&arr); return NULL; } const char usage[] = "usage: vali generate [options...]
\n"; int main(int argc, char *argv[]) { if (argc < 2 || strcmp(argv[1], "generate") != 0) { fprintf(stderr, "%s", usage); return 1; } while (1) { const struct option options[] = { { .name = "prefix", .has_arg = required_argument }, {0}, }; int i = -1; int opt = getopt_long(argc - 1, &argv[1], "", options, &i); if (opt < 0) { break; } else if (opt != 0) { return 1; } const char *optname = options[i].name; if (strcmp(optname, "prefix") == 0) { prefix = strdup(optarg); } else { abort(); // unreachable } } if (optind + 4 != argc) { fprintf(stderr, "%s", usage); return 1; } const char *input_filename = argv[optind + 1]; const char *header_filename = argv[optind + 2]; const char *code_filename = argv[optind + 3]; FILE *f = fopen(input_filename, "r"); if (f == NULL) { perror("Failed to open definition file"); return 1; } char *def = read_full(f); fclose(f); if (def == NULL) { fprintf(stderr, "Failed to read interface definition file\n"); return 1; } f = fmemopen(def, strlen(def), "r"); if (f == NULL) { perror("fmemopen() failed"); return 1; } iface = varlink_interface_read(f); fclose(f); if (iface == NULL) { fprintf(stderr, "Failed to parse interface definition\n"); return 1; } if (prefix == NULL) { prefix = strdup(iface->name); for (size_t i = 0; prefix[i] != '\0'; i++) { if (prefix[i] == '-' || prefix[i] == '.') { prefix[i] = '_'; } } } FILE *header_file = fopen(header_filename, "w"); if (header_file == NULL) { perror("Failed to open header file"); return 1; } FILE *code_file = fopen(code_filename, "w"); if (code_file == NULL) { perror("Failed to open code file"); return 1; } const char *last_slash = strrchr(header_filename, '/'); const char *header_basename; if (last_slash != NULL) { header_basename = &last_slash[1]; } else { header_basename = header_filename; } gen_header(header_file, iface); gen_code(code_file, iface, header_basename, def); free(prefix); varlink_interface_destroy(iface); free(def); if (fclose(header_file) != 0) { perror("Failed to close header file"); return 1; } if (fclose(code_file) != 0) { perror("Failed to close code file"); return 1; } return 0; } vali-0.1.0/util.c000066400000000000000000000010321507023532100135560ustar00rootroot00000000000000#include #include "util.h" void *array_add(struct array *arr, size_t size) { size_t new_cap = arr->cap; if (new_cap == 0) { new_cap = 64; } while (new_cap < arr->size + size) { new_cap *= 2; } if (arr->cap != new_cap) { void *new_data = realloc(arr->data, new_cap); if (new_data == NULL) { abort(); } arr->data = new_data; arr->cap = new_cap; } char *data = arr->data; void *entry = &data[arr->size]; arr->size += size; return entry; } void array_finish(struct array *arr) { free(arr->data); }