diff --git a/Makefile b/Makefile index 176dc2b..4940b82 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC?=gcc -CFLAGS+=-Werror -Wall -Wextra -std=c99 -ggdb -fsanitize=address +CFLAGS+=-Wall -Wextra -std=c99 -ggdb -fsanitize=address -jitterbug: wire.c server.c main.c +jitterbug: util.c wire.c match.c server.c main.c $(CC) $(CFLAGS) -o $@ $^ diff --git a/main.c b/main.c index ad9263f..7083319 100644 --- a/main.c +++ b/main.c @@ -3,6 +3,7 @@ #include #include #include +#include "match.h" #include "server.h" const char *arg_shift(int *argc, char ***argv) { diff --git a/match.c b/match.c new file mode 100644 index 0000000..8d304be --- /dev/null +++ b/match.c @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include "match.h" +#include "util.h" +#include "wire.h" +#include "try.h" + +void match_rule_free(match_rule_t *rule) +{ + if (rule) { + free(rule->sender); + free(rule->interface); + free(rule->member); + free(rule->path); + free(rule->path_namespace); + free(rule->destination); + free(rule->arg0namespace); + for (int i = 0; i < MATCH_RULE_MAX_ARG; i++) { + free(rule->arg[i]); + free(rule->arg_path[i]); + } + free(rule); + } +} + +match_rule_t *match_rule_from_string(char *d) +{ + char *key = NULL; + char acc[MATCH_RULE_MAX]; + int acci = 0; + match_rule_t *rule; + + if (strlen(d) >= MATCH_RULE_MAX) { + return NULL; + } + + rule = calloc(sizeof(match_rule_t), 1); + if (!rule) { + return NULL; + } + + while (*d) { + /* key */ + key = d; + while (*d && *d != '=') d++; + if (*d != '=') { + goto fail; + } + *d = '\0'; + d++; + if (!*d) { + goto fail; + } + + /* value */ + bool quote = false; + acci = 0; + acc[acci] = '\0'; + for (;;) { + if (*d == '\'') { + /* '\'' starts a quoted region */ + quote = !quote; + d++; + } else if (!quote && *d == ',') { + /* ',' outside quoted region means next key-value pair */ + acc[acci] = '\0'; + acci = 0; + d++; + break; + } else if (!quote && *d == '\\') { + /* '\\' outside quoted region means escaped character */ + /* TODO: not entirely to spec */ + d++; + if (*d != '\'') { + goto fail; + } + if (acci >= MATCH_RULE_MAX) { + goto fail; + } + acc[acci++] = *d; + d++; + } else if (!*d) { + /* end of string */ + if (quote) { + /* end of string, yet we were insinde a quoted region */ + /* unexpected end of input */ + goto fail; + } + acc[acci] = '\0'; + acci = 0; + break; + } else if (quote) { + if (acci >= MATCH_RULE_MAX) { + goto fail; + } + acc[acci++] = *d; + d++; + } else { + goto fail; + } + } + + if (strcmp(key, "type") == 0 && !rule->type) { + if (strcmp(acc, "signal") == 0) rule->type = DBUS_MESSAGE_SIGNAL; + else if (strcmp(acc, "method_call") == 0) rule->type = DBUS_MESSAGE_METHOD_CALL; + else if (strcmp(acc, "method_return") == 0) rule->type = DBUS_MESSAGE_METHOD_RETURN; + else if (strcmp(acc, "error") == 0) rule->type = DBUS_MESSAGE_ERROR; + else goto fail; + } else if (strcmp(key, "sender") == 0 && !rule->sender) { + rule->sender = string_dup(acc); + } else if (strcmp(key, "interface") == 0 && !rule->interface) { + rule->interface = string_dup(acc); + } else if (strcmp(key, "member") == 0 && !rule->member) { + rule->member = string_dup(acc); + } else if (strcmp(key, "path") == 0 && !rule->path && !rule->path_namespace) { + rule->path = string_dup(acc); + } else if (strcmp(key, "path_namespace") == 0 && !rule->path && !rule->path_namespace) { + rule->path_namespace = string_dup(acc); + } else if (strcmp(key, "destination") == 0 && !rule->destination) { + rule->destination = string_dup(acc); + } else if (strcmp(key, "arg0namespace") == 0 && !rule->arg0namespace) { + rule->arg0namespace = string_dup(acc); + } else if (strncmp(key, "arg", 3) == 0) { + char *part = key + 3; + if (!*part) { + goto fail; + } + + // thanks: https://github.com/bus1/dbus-broker/blob/55fe5443d88ee2b93afa340b82ad89508ff017e2/src/bus/match.c#L105 + size_t partn = strlen(part); + uint32_t index = 0; + for (unsigned int i = 0; i < 2 && partn; i++, part++, --partn) { + if (*part >= '0' && *part <= '9') { + index = index * 10 + *part - '0'; + } else { + break; + } + } + + if (index == 0 && rule->arg0namespace) { + goto fail; + } + if (index >= MATCH_RULE_MAX_ARG) { + goto fail; + } + if (rule->arg[index] || rule->arg_path[index]) { + goto fail; + } + + if (*part == '\0') { + rule->arg[index] = string_dup(acc); + } else if (strcmp(part, "path") == 0) { + rule->arg_path[index] = string_dup(acc); + } else { + goto fail; + } + } else { + goto fail; + } + } + + return rule; +fail: + match_rule_free(rule); + return NULL; +} + +#define _check_header_field_str(M_rule_field, M_header_field) \ + do { \ + if (rule->M_rule_field && (!msg->fields[M_header_field].present || strcmp(rule->M_rule_field, msg->fields[M_header_field].t.str) != 0)) { \ + return -1; \ + } \ + } while(0) + +int match_rule_check(match_rule_t *rule, wire_message_t *msg, wire_context_t *ctx) +{ + if (rule->type && msg->type != rule->type) { + return -1; + } + + _check_header_field_str(sender, DBUS_HEADER_FIELD_SENDER); + _check_header_field_str(interface, DBUS_HEADER_FIELD_INTERFACE); + _check_header_field_str(member, DBUS_HEADER_FIELD_MEMBER); + _check_header_field_str(path, DBUS_HEADER_FIELD_PATH); + /* todo: path_namespace */ + _check_header_field_str(destination, DBUS_HEADER_FIELD_DESTINATION); + + wire_message_body_string_t strings[MATCH_RULE_MAX_ARG]; + memset(strings, 0, sizeof(strings)); + TRYST(wire_collect_strings(ctx, msg, strings, MATCH_RULE_MAX_ARG)); + + for (int i = 0; i < MATCH_RULE_MAX_ARG; i++) { + if (rule->arg[i] && (!strings[i].str || strcmp(strings[i].str, rule->arg[i]) != 0)) { + return -1; + } + /* todo: arg_path */ + } + + /* todo: arg0namespace */ + + return 0; +} diff --git a/match.h b/match.h new file mode 100644 index 0000000..9050804 --- /dev/null +++ b/match.h @@ -0,0 +1,23 @@ +#ifndef _JITTERBUG__MATCH_H +#define _JITTERBUG__MATCH_H + +#include +#include +#include "wire.h" + +#define MATCH_RULE_MAX 1024 +#define MATCH_RULE_MAX_ARG 12 + +typedef struct match_rule { + uint8_t type; + bool eavesdrop; + char *sender, *interface, *member, *path, *path_namespace, *destination, *arg0namespace; + char *arg[MATCH_RULE_MAX_ARG]; /* TODO: spec states that indexes 0 to 63 should be supported */ + char *arg_path[MATCH_RULE_MAX_ARG]; /* TODO: spec states that indexes 0 to 63 should be supported */ +} match_rule_t; + +void match_rule_free(match_rule_t *rule); +match_rule_t *match_rule_from_string(char *d); +int match_rule_check(match_rule_t *rule, wire_message_t *msg, wire_context_t *ctx); + +#endif // _JITTERBUG__MATCH_H diff --git a/server.c b/server.c index bf204b8..4c6cf53 100644 --- a/server.c +++ b/server.c @@ -6,16 +6,11 @@ #include #include #include +#include "match.h" #include "try.h" #include "server.h" #include "wire.h" - -char *string_dup(const char *s) -{ - size_t len = strlen(s) + 1; - char *p = malloc(len); - return p ? memcpy(p, s, len) : NULL; -} +#include "util.h" // https://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function#FNV-1a_hash @@ -98,6 +93,19 @@ int jb_server_name_add(struct jb_server *s, char *name, int client_index) return -1; } +int jb_server_match_add(struct jb_server *s, char *match, int client_index) +{ + for (int i = 0; i < JB_MAX_MATCH; i++) { + if (s->matches[i].client_index < 0) { + s->matches[i].rule = match_rule_from_string(match); + s->matches[i].client_index = client_index; + return 0; + } + } + + return -1; +} + int jb_server_client_assign_unique_name(struct jb_server *s, int i) { struct jb_client *c = &s->clients[i]; @@ -166,6 +174,14 @@ void jb_server_client_remove(struct jb_server *s, int i) if (s->clients[i].fd >= 0) { close(s->clients[i].fd); } + // TODO: slow + for (int i = 0; i < JB_MAX_MATCH; i++) { + if (s->matches[i].client_index == i) { + s->matches[i].client_index = -1; + match_rule_free(s->matches[i].rule); + s->matches[i].rule = NULL; + } + } jb_server_name_remove(s, s->clients[i].unique_name_index); jb_server_name_remove(s, s->clients[i].owned_name_index); s->clients[i].unique_name_index = -1; @@ -193,6 +209,29 @@ ssize_t jb_server_client_recv(struct jb_server *s, int i, void *buf, size_t n) return status; } +int jb_server_broadcast_message(struct jb_server *s, wire_message_t *msg, wire_context_t *ctx, char *sender_unique_name) +{ + for (int i = 0; i < JB_MAX_MATCH; i++) { + if (s->matches[i].rule) { + if (match_rule_check(s->matches[i].rule, msg, ctx) >= 0) { + struct jb_client *client = &s->clients[s->matches[i].client_index]; + + uint8_t reply_data[4096]; + memset(reply_data, 0, 4096); + wire_context_t reply_ctx = { + .byte_cursor = 0, + .data = reply_data, + .data_len = 4096, + }; + + TRYST(wire_compose_unicast_reply(&reply_ctx, ctx, msg, sender_unique_name)); + TRYST(send(client->fd, reply_data, reply_ctx.byte_cursor, 0)); + } + } + } + return 0; +} + #define _reply_begin(M_sig) \ do { \ TRYST(wire_compose_reply(&reply_ctx, &msg, (M_sig), &body_length)); \ @@ -205,6 +244,7 @@ ssize_t jb_server_client_recv(struct jb_server *s, int i, void *buf, size_t n) if (send(s->fds[i].fd, reply_data, reply_ctx.byte_cursor, 0) != reply_ctx.byte_cursor) { \ return -1; \ } \ + printf("send: sent %d bytes!\n", reply_ctx.byte_cursor); \ } while(0); \ #define _reply_error(message) \ @@ -233,7 +273,7 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, wire_message_field_t *destination_field = &msg.fields[DBUS_HEADER_FIELD_DESTINATION]; wire_message_field_t *member_field = &msg.fields[DBUS_HEADER_FIELD_MEMBER]; - bool should_unicast = false; + bool should_forward = false; uint32_t *body_length; uint32_t body_start; @@ -247,15 +287,20 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, switch (msg.type) { case DBUS_MESSAGE_METHOD_CALL: { - if (!destination_field->present || !member_field->present) { + if (!member_field->present) { return -1; } + if (!destination_field->present) { + should_forward = true; + break; + } + char *member = member_field->t.str; if (strcmp(destination_field->t.str, "org.freedesktop.DBus") != 0) { // not for dbus. - should_unicast = true; + should_forward = true; break; } @@ -277,10 +322,7 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, return -1; } - char *name_str = string_dup(name); - if (!name_str) { - return -1; - } + char *name_str = TRYPTR(string_dup(name)); int status_code = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; @@ -332,6 +374,7 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, } else if (strcmp(member, "ListNames") == 0) { _reply_begin("as") { TRYPTR(wire_set_u32(&reply_ctx, s->names_count)); + printf("ListNames %d\n", s->names_count); int left = s->names_count; for (int i = 0; i < JB_MAX_NAMES; i++) { @@ -346,7 +389,7 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, } _reply_end() } else if (strcmp(member, "ListActivatableNames") == 0) { // TODO: stub (always returns empty array) - printf("STUB: ListActivatableNames\n"); + printf("FIXME: STUB: ListActivatableNames\n"); _reply_begin("as") { TRYPTR(wire_set_u32(&reply_ctx, 0)); @@ -361,14 +404,40 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, if (name_len < 1 || name_len > 256) { return -1; } + /* unused flags value */ + TRYPTR(wire_get_u32(&ctx)); - printf("STUB: StartServiceByName: %s\n", name); + printf("FIXME: STUB: StartServiceByName: %s\n", name); _reply_begin("u") { TRYPTR(wire_set_u32(&reply_ctx, 1)); } _reply_end() - return 0; + } else if (strcmp(member, "AddMatch") == 0) { + char *match = TRYPTR(wire_get_string(&ctx)); + int match_len = strlen(match); + if (match_len < 1 || match_len > MATCH_RULE_MAX) { + return -1; + } + + TRYST(jb_server_match_add(s, match, i)); + + printf("client index %d just added match rule: '%s'\n", i, match); + + _reply_begin("") {} _reply_end() + } else if (strcmp(member, "RemoveMatch") == 0) { + // TODO: stub (does nothing and always returns success) + + char *match = TRYPTR(wire_get_string(&ctx)); + int match_len = strlen(match); + if (match_len < 1 || match_len > MATCH_RULE_MAX) { + return -1; + } + + printf("FIXME: STUB: RemoveMatch: %s\n", match); + + _reply_begin("") {} _reply_end() } else { + printf("FIXME: daemon method '%s' is not implemented or invalid\n", member); _reply_error("org.freedesktop.DBus.Error.UnknownMethod"); return 0; } @@ -379,7 +448,11 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, return -1; } - should_unicast = true; + should_forward = true; + } break; + + case DBUS_MESSAGE_SIGNAL: { + should_forward = true; } break; default: { @@ -388,17 +461,23 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, } break; } - if (should_unicast) { - if (client->unique_name_index < 0 || !destination_field->present) { + if (should_forward) { + if (client->unique_name_index < 0) { return -1; } - struct jb_client *target = jb_server_name_find_client(s, destination_field->t.str); - if (!target) { - _reply_error("org.freedesktop.DBus.Error.NameHasNoOwner"); - return 0; + if (destination_field->present) { + /* unicast */ + struct jb_client *target = jb_server_name_find_client(s, destination_field->t.str); + if (!target) { + _reply_error("org.freedesktop.DBus.Error.NameHasNoOwner"); + return 0; + } + TRYST(wire_compose_unicast_reply(&reply_ctx, &ctx, &msg, s->names[client->unique_name_index].name)); + TRYST(send(target->fd, reply_data, reply_ctx.byte_cursor, 0)); + } else { + /* broadcast */ + TRYST(jb_server_broadcast_message(s, &msg, &ctx, s->names[client->unique_name_index].name)); } - TRYST(wire_compose_unicast_reply(&reply_ctx, &ctx, &msg, s->names[client->unique_name_index].name)); - TRYST(send(target->fd, reply_data, reply_ctx.byte_cursor, 0)); } return 0; @@ -414,6 +493,12 @@ void jb_server_free(struct jb_server *s) free(s->names[i].name); } } + // matches are allocated on the heap + for (int i = 0; i < JB_MAX_MATCH; i++) { + if (s->matches[i].rule) { + match_rule_free(s->matches[i].rule); + } + } free(s); } @@ -462,6 +547,12 @@ struct jb_server *jb_server_create(const char *socket_path) s->names[i].client_index = -1; s->names[i].name = NULL; } + s->names_count = 0; + + for (int i = 0; i < JB_MAX_MATCH; i++) { + s->matches[i].client_index = -1; + s->matches[i].rule = NULL; + } s->fds[JB_MAX_CLIENTS].fd = s->sock_fd; s->fds[JB_MAX_CLIENTS].events = POLLIN; diff --git a/server.h b/server.h index daf1ee3..a95817b 100644 --- a/server.h +++ b/server.h @@ -1,12 +1,14 @@ #ifndef _JITTERBUG__SERVER_H #define _JITTERBUG__SERVER_H +#include "match.h" #include #include // TODO: dynamically size the arrays #define JB_MAX_CLIENTS 256 #define JB_MAX_NAMES 512 +#define JB_MAX_MATCH 512 #define JB_BACKLOG 12 enum { @@ -25,7 +27,12 @@ struct jb_client { struct jb_name { char *name; - int16_t client_index; + int32_t client_index; +}; + +struct jb_match { + match_rule_t *rule; + int32_t client_index; }; struct jb_server { @@ -35,6 +42,7 @@ struct jb_server { struct jb_client clients[JB_MAX_CLIENTS]; struct jb_name names[JB_MAX_NAMES]; struct pollfd fds[JB_MAX_CLIENTS + 1]; + struct jb_match matches[JB_MAX_MATCH]; }; diff --git a/util.c b/util.c new file mode 100644 index 0000000..287b505 --- /dev/null +++ b/util.c @@ -0,0 +1,8 @@ +#include "util.h" + +char *string_dup(const char *s) +{ + size_t len = strlen(s) + 1; + char *p = (char*)malloc(len); + return p ? (char*)memcpy(p, s, len) : NULL; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..675e3a2 --- /dev/null +++ b/util.h @@ -0,0 +1,11 @@ +#ifndef _JITTERBUG__UTIL_H +#define _JITTERBUG__UTIL_H + +#include +#include +#include +#include + +char *string_dup(const char *s); + +#endif // _JITTERBUG__UTIL_H \ No newline at end of file diff --git a/wire.c b/wire.c index 28d7564..6b84dde 100644 --- a/wire.c +++ b/wire.c @@ -3,7 +3,7 @@ #include "try.h" #include "wire.h" -#define WIRE_ENABLE_VERBOSE 0 +#define WIRE_ENABLE_VERBOSE 1 #if WIRE_ENABLE_VERBOSE #define WIRE_VERBOSE printf #else @@ -118,7 +118,7 @@ int wire_parse_message(wire_context_t *c, wire_message_t *msg) size_t header_fields_end = c->byte_cursor + msg->header_fields_length; - WIRE_VERBOSE("start parse message: type=%d\n", msg->type); + WIRE_VERBOSE("\nstart parse message: type=%d, no_reply_expected=%d\n", msg->type, msg->flags & DBUS_FLAG_NO_REPLY_EXPECTED); while (c->byte_cursor < header_fields_end) { uint8_t field_code = *(uint8_t*)TRYPTR(wire_get_u8(c)); @@ -271,7 +271,6 @@ int wire_compose_error(wire_context_t *c, wire_message_t *msg, const char *error int wire_compose_unicast_reply(wire_context_t *c, wire_context_t *msg_c, wire_message_t *msg, char *sender_unique_name) { - TRYPTR(wire_set_u8(c, msg->endianness)); /* endianness */ TRYPTR(wire_set_u8(c, msg->type)); /* type */ TRYPTR(wire_set_u8(c, 0)); /* flags */ @@ -366,3 +365,74 @@ int wire_compose_unicast_reply(wire_context_t *c, wire_context_t *msg_c, wire_me return 0; } + +int wire_collect_strings(wire_context_t *c, wire_message_t *msg, wire_message_body_string_t *strings, int strings_count) +{ + if (!msg->fields[DBUS_HEADER_FIELD_SIGNATURE].present) { + return -1; + } + + char *sig = msg->fields[DBUS_HEADER_FIELD_SIGNATURE].t.str; + int stri = 0; + int sigi = 0; + uint32_t body_cursor = c->byte_cursor; + + while (*sig) { + switch (*sig) { + case 'y': { + TRYPTR(wire_get_u8(c)); + } break; + case 'b': { + TRYPTR(wire_get_u32(c)); + } break; + case 'n': { + TRYPTR(wire_get_i16(c)); + } break; + case 'q': { + TRYPTR(wire_get_u16(c)); + } break; + case 'i': { + TRYPTR(wire_get_i32(c)); + } break; + case 'u': { + TRYPTR(wire_get_u32(c)); + } break; + case 'o': /* through */ + case 's': { + char *s = TRYPTR(wire_get_string(c)); + if (sigi >= strings_count) { + return -1; + } + strings[sigi].index = sigi; + strings[sigi].str = s; + } break; + case 'g': { + char *s = TRYPTR(wire_get_signature(c)); + if (sigi >= strings_count) { + return -1; + } + strings[sigi].index = sigi; + strings[sigi].str = s; + } break; + case 'a': { + uint32_t array_size = *(uint32_t*)TRYPTR(wire_get_u32(c)); + uint32_t new_cursor = c->byte_cursor + array_size; + if (new_cursor >= c->data_len) { + return -1; + } + c->byte_cursor = new_cursor; + } break; + + default: { + return -1; + } + } + + sigi++; + sig++; + } + + c->byte_cursor = body_cursor; + + return stri; +} diff --git a/wire.h b/wire.h index 412d412..4be6b8e 100644 --- a/wire.h +++ b/wire.h @@ -8,23 +8,17 @@ #define DBUS_PROTOCOL_VERSION 1 -/* The caller is now the primary owner of the name, replacing any previous owner. - Either the name had no owner before, or the caller specified DBUS_NAME_FLAG_REPLACE_EXISTING - and the current owner specified DBUS_NAME_FLAG_ALLOW_REPLACEMENT. */ #define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 -/* The name already had an owner, DBUS_NAME_FLAG_DO_NOT_QUEUE was not specified, - and either the current owner did not specify DBUS_NAME_FLAG_ALLOW_REPLACEMENT or - the requesting application did not specify DBUS_NAME_FLAG_REPLACE_EXISTING. */ #define DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2 -/* The name already has an owner, DBUS_NAME_FLAG_DO_NOT_QUEUE was specified, - and either DBUS_NAME_FLAG_ALLOW_REPLACEMENT was not specified by the current owner, - or DBUS_NAME_FLAG_REPLACE_EXISTING was not specified by the requesting application. */ #define DBUS_REQUEST_NAME_REPLY_EXISTS 3 -/* The application trying to request ownership of a name is already the owner of it. */ #define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 +#define DBUS_FLAG_NO_REPLY_EXPECTED 0x1 +#define DBUS_FLAG_NO_AUTO_START 0x1 +#define DBUS_FLAG_ALLOW_INTERACTIVE_AUTHORIZATION 0x1 + enum { - DBUS_HEADER_FIELD_INVALID, + DBUS_HEADER_FIELD_INVALID = 0, DBUS_HEADER_FIELD_PATH, DBUS_HEADER_FIELD_INTERFACE, DBUS_HEADER_FIELD_MEMBER, @@ -37,7 +31,7 @@ enum { }; enum { - DBUS_MESSAGE_INVALID, + DBUS_MESSAGE_INVALID = 0, DBUS_MESSAGE_METHOD_CALL, DBUS_MESSAGE_METHOD_RETURN, DBUS_MESSAGE_ERROR, @@ -65,6 +59,10 @@ typedef struct wire_message { uint8_t header_fields_count; wire_message_field_t fields[WIRE_MESSAGE_MAX_HEADER_FIELDS]; /* cannot have more than 9 header fields */ } wire_message_t; +typedef struct wire_message_body_string { + uint16_t index; + char *str; +} wire_message_body_string_t; #define _WIRE_DECL_BYTE_TYPE(M_mnemonic, M_type) \ M_type *wire_get_##M_mnemonic(wire_context_t *c); \ @@ -129,6 +127,7 @@ int wire_parse_message(wire_context_t *c, wire_message_t *msg); int wire_compose_reply(wire_context_t *c, wire_message_t *msg, const char *signature, uint32_t **out_body_length); int wire_compose_error(wire_context_t *c, wire_message_t *msg, const char *error_name); int wire_compose_unicast_reply(wire_context_t *c, wire_context_t *msg_c, wire_message_t *msg, char *sender_unique_name); +int wire_collect_strings(wire_context_t *c, wire_message_t *msg, wire_message_body_string_t *strings, int strings_count); static inline char *wire_get_signature(wire_context_t *c) { return wire_get_string_impl(c, true);