very barebones match implementation

This commit is contained in:
hippoz 2022-12-29 20:10:36 +02:00
parent fb2ea18aad
commit 2a07d8dd38
Signed by: hippoz
GPG key ID: 56C4E02A85F2FBED
10 changed files with 459 additions and 44 deletions

View file

@ -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 $@ $^

1
main.c
View file

@ -3,6 +3,7 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "match.h"
#include "server.h"
const char *arg_shift(int *argc, char ***argv) {

204
match.c Normal file
View file

@ -0,0 +1,204 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#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;
}

23
match.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef _JITTERBUG__MATCH_H
#define _JITTERBUG__MATCH_H
#include <stdint.h>
#include <stdbool.h>
#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

143
server.c
View file

@ -6,16 +6,11 @@
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#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;

View file

@ -1,12 +1,14 @@
#ifndef _JITTERBUG__SERVER_H
#define _JITTERBUG__SERVER_H
#include "match.h"
#include <poll.h>
#include <stdint.h>
// 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];
};

8
util.c Normal file
View file

@ -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;
}

11
util.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef _JITTERBUG__UTIL_H
#define _JITTERBUG__UTIL_H
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
char *string_dup(const char *s);
#endif // _JITTERBUG__UTIL_H

76
wire.c
View file

@ -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;
}

23
wire.h
View file

@ -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);