diff --git a/main.c b/main.c index 2df9d26..5258b0f 100644 --- a/main.c +++ b/main.c @@ -3,19 +3,6 @@ #include #include "server.h" -// https://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function#FNV-1a_hash -uint64_t hashmap_hash(const char *bytes, size_t bytes_n, size_t map_len) -{ - uint64_t hash = 0xcbf29ce484222325; - - for (size_t i = 0; i < bytes_n; i++) { - hash *= 0x100000001b3; - hash ^= bytes[i]; - } - - return (hash % map_len); -} - const char *arg_shift(int *argc, char ***argv) { if (*argc < 1) return NULL; diff --git a/server.c b/server.c index 2a7a497..0e4c614 100644 --- a/server.c +++ b/server.c @@ -5,15 +5,39 @@ #include #include #include +#include #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; +} + + +// https://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function#FNV-1a_hash +uint64_t hashmap_hash(const char *bytes, size_t bytes_n, size_t map_len) +{ + uint64_t hash = 0xcbf29ce484222325; + + for (size_t i = 0; i < bytes_n; i++) { + hash *= 0x100000001b3; + hash ^= bytes[i]; + } + + return (hash % map_len); +} + int jb_server_client_add(struct jb_server *s, int fd) { for (int i = 0; i < JB_MAX_CLIENTS; i++) { if (s->clients[i].fd < 0) { s->clients[i].fd = fd; + s->clients[i].owned_name_index = -1; + s->clients[i].unique_name_index = -1; s->clients[i].state = JB_CLIENT_STATE_WAIT_AUTH; s->fds[i].fd = fd; s->fds[i].events = POLLIN; @@ -23,11 +47,107 @@ int jb_server_client_add(struct jb_server *s, int fd) return -1; } +int jb_server_name_find(struct jb_server *s, char *name) +{ + int bucket = hashmap_hash(name, strlen(name), JB_MAX_NAMES); + + for (int i = bucket; i < bucket + 12 && i < JB_MAX_NAMES; i++) { + if (s->names[i].name && strcmp(s->names[i].name, name) == 0) { + return i; + } + } + + return -1; +} + +int jb_server_name_add(struct jb_server *s, char *name, int client_index) +{ + if (jb_server_name_find(s, name) >= 0) { + return -1; + } + + int bucket = hashmap_hash(name, strlen(name), JB_MAX_NAMES); + + for (int i = bucket; i < bucket + 12 && i < JB_MAX_NAMES; i++) { + if (s->names[i].client_index == -1) { + s->names[i].client_index = client_index; + s->names[i].name = name; + return i; + } + } + + return -1; +} + +int jb_server_client_assign_unique_name(struct jb_server *s, int i) +{ + struct jb_client *c = &s->clients[i]; + if (c->unique_name_index != -1) { + return -1; + } + uint32_t id = 0; + FILE *urandom_file = fopen("/dev/urandom", "rb"); + if (!urandom_file) { + return -1; + } + if (fread(&id, 1, sizeof(uint32_t), urandom_file) != sizeof(uint32_t)) { + return -1; + } + char *name = malloc(sizeof(char) * 16); + if (!name) { + return -1; + } + + if (snprintf(name, 16, ":%"PRIu32, id) < 0) { + return -1; + } + + int name_index = jb_server_name_add(s, name, i); + if (name_index < 0) { + free(name); + return -1; + } + c->unique_name_index = name_index; + + return name_index; +} + +int jb_server_client_assign_own_name(struct jb_server *s, int i, char *name) +{ + struct jb_client *c = &s->clients[i]; + if (c->owned_name_index != -1) { + return -1; + } + + int name_index = jb_server_name_add(s, name, i); + if (name_index < 0) { + return -1; + } + c->owned_name_index = name_index; + + return 0; +} + +void jb_server_name_remove(struct jb_server *s, int i) +{ + if (i >= 0) { + if (s->names[i].name) { + free(s->names[i].name); + s->names[i].name = NULL; + } + s->names[i].client_index = -1; + } +} + void jb_server_client_remove(struct jb_server *s, int i) { if (s->clients[i].fd >= 0) { close(s->clients[i].fd); } + 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; + s->clients[i].owned_name_index = -1; s->clients[i].fd = -1; s->clients[i].state = JB_CLIENT_STATE_NONE; s->fds[i].fd = -1; @@ -53,6 +173,8 @@ ssize_t jb_server_client_recv(struct jb_server *s, int i, void *buf, size_t n) int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, size_t data_len) { + struct jb_client *client = &s->clients[i]; + wire_context_t ctx = { .byte_cursor = 0, .data = data, @@ -63,7 +185,13 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, TRY_NONNEGATIVE(int, wire_parse_message(&ctx, &msg), -1); - printf("length: %d, serial: %d, header fields length in bytes: %d\n", msg.body_length, msg.serial, msg.header_fields_length); + uint8_t reply_data[512]; + memset(reply_data, 0, 512); + wire_context_t reply_ctx = { + .byte_cursor = 0, + .data = reply_data, + .data_len = 511, + }; switch (msg.type) { case DBUS_MESSAGE_METHOD_CALL: { @@ -83,26 +211,57 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, if (for_dbus) { if (strcmp(member, "Hello") == 0) { - printf("jb_server_client_process_message: message was for dbus, got hello\n"); - uint8_t reply_data[512]; - memset(reply_data, 0, 512); - wire_context_t reply_ctx = { - .byte_cursor = 0, - .data = reply_data, - .data_len = 511, - }; + int unique_name_index = jb_server_client_assign_unique_name(s, i); + if (unique_name_index < 0) { + return -1; + } uint32_t *body_length; TRY_NONNEGATIVE(int, wire_compose_reply(&reply_ctx, &msg, "s", &body_length), -1); uint32_t body_start = reply_ctx.byte_cursor; - TRY_NONNULL(char*, wire_set_string(&reply_ctx, ":1.0"), -1); + TRY_NONNULL(char*, wire_set_string(&reply_ctx, s->names[unique_name_index].name), -1); *body_length = reply_ctx.byte_cursor - body_start; if (send(s->fds[i].fd, reply_data, reply_ctx.byte_cursor, 0) != reply_ctx.byte_cursor) { return -1; } - printf("jb_server_client_process_message: processed hello\n"); + printf("assigned unique name '%s' to connection %d\n", s->names[unique_name_index].name, i); + } else if (strcmp(member, "RequestName") == 0) { + char *name = TRY_NONNULL(char*, wire_get_string(&ctx, NULL), -1); + int name_len = strlen(name); + if (name_len < 1 || name_len > 256) { + return -1; + } + + char *name_str = string_dup(name); + if (!name_str) { + return -1; + } + + int status_code = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; + + if (jb_server_client_assign_own_name(s, i, name_str) < 0) { + free(name_str); + // TODO: report the actual error + status_code = DBUS_REQUEST_NAME_REPLY_EXISTS; + } + + uint32_t *body_length; + TRY_NONNEGATIVE(int, wire_compose_reply(&reply_ctx, &msg, "u", &body_length), -1); + uint32_t body_start = reply_ctx.byte_cursor; + TRY_NONNULL(uint32_t*, wire_set_u32(&reply_ctx, status_code), -1); + *body_length = reply_ctx.byte_cursor - body_start; + if (send(s->fds[i].fd, reply_data, reply_ctx.byte_cursor, 0) != reply_ctx.byte_cursor) { + return -1; + } + + if (status_code == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + printf("client '%s' (index=%d) now owns name '%s'\n", s->names[client->unique_name_index].name, i, name_str); + } } else { - printf("jb_server_client_process_message: message was for dbus, however got unimplemented member '%s'\n", member); + TRY_NONNEGATIVE(int, wire_compose_error(&reply_ctx, &msg, "org.freedesktop.DBus.Error.UnknownMethod"), -1); + if (send(s->fds[i].fd, reply_data, reply_ctx.byte_cursor, 0) != reply_ctx.byte_cursor) { + return -1; + } } } else { printf("jb_server_client_process_message: skipping message not for dbus for now\n"); @@ -110,7 +269,11 @@ int jb_server_client_process_message(struct jb_server *s, int i, uint8_t *data, } break; default: { - printf("jb_server_client_process_message: unimplemented message type: %d\n", msg.type); + TRY_NONNEGATIVE(int, wire_compose_error(&reply_ctx, &msg, "xyz.hippoz.jitterbug.NotImplemented"), -1); + if (send(s->fds[i].fd, reply_data, reply_ctx.byte_cursor, 0) != reply_ctx.byte_cursor) { + return -1; + } + return -1; } break; } @@ -157,12 +320,19 @@ struct jb_server *jb_server_create(const char *socket_path) for (int i = 0; i < JB_MAX_CLIENTS; i++) { s->clients[i].fd = -1; + s->clients[i].owned_name_index = -1; + s->clients[i].unique_name_index = -1; s->clients[i].state = JB_CLIENT_STATE_NONE; s->fds[i].fd = -1; s->fds[i].events = 0; s->fds[i].revents = 0; } + for (int i = 0; i < JB_MAX_NAMES; i++) { + s->names[i].client_index = -1; + s->names[i].name = NULL; + } + s->fds[JB_MAX_CLIENTS].fd = s->sock_fd; s->fds[JB_MAX_CLIENTS].events = POLLIN; s->fd_num = JB_MAX_CLIENTS + 1; @@ -254,7 +424,9 @@ int jb_server_turn(struct jb_server *s) } break; case JB_CLIENT_STATE_READY: { - jb_server_client_process_message(s, i, (uint8_t*)data, data_buffer_len); + if (jb_server_client_process_message(s, i, (uint8_t*)data, data_buffer_len) < 0) { + _client_die("failed to process message"); + } } break; case JB_CLIENT_STATE_NONE: {} /* through */ diff --git a/server.h b/server.h index c6cf61d..e7b4180 100644 --- a/server.h +++ b/server.h @@ -2,9 +2,12 @@ #define _JITTERBUG__SERVER_H #include +#include -#define JB_MAX_CLIENTS 128 -#define JB_BACKLOG 10 +// TODO: dynamically size the arrays +#define JB_MAX_CLIENTS 256 +#define JB_MAX_NAMES 512 +#define JB_BACKLOG 12 enum { JB_CLIENT_STATE_NONE, @@ -15,13 +18,21 @@ enum { struct jb_client { int fd; - int state; + uint8_t state; + int16_t unique_name_index; + int16_t owned_name_index; +}; + +struct jb_name { + char *name; + int16_t client_index; }; struct jb_server { int sock_fd; int fd_num; struct jb_client clients[JB_MAX_CLIENTS]; + struct jb_name names[JB_MAX_NAMES]; struct pollfd fds[JB_MAX_CLIENTS + 1]; }; diff --git a/wire.c b/wire.c index ccf8e87..be01dbc 100644 --- a/wire.c +++ b/wire.c @@ -157,6 +157,11 @@ int wire_parse_message(wire_context_t *c, wire_message_t *msg) msg->fields[msg->header_fields_count].t.str = str; printf("DBUS_HEADER_FIELD_DESTINATION: %s\n", str); } break; + case DBUS_HEADER_FIELD_SIGNATURE: { + char *str = TRY_NONNULL(char*, wire_get_signature(c, NULL), -1); + msg->fields[msg->header_fields_count].t.str = str; + printf("DBUS_HEADER_FIELD_SIGNATURE: %s\n", str); + } break; default: { printf("header: unknown field: %d\n", field_code); @@ -173,6 +178,9 @@ int wire_parse_message(wire_context_t *c, wire_message_t *msg) } } + /* header ends at 8 byte boundry */ + TRY_NONNULL(bool, wire_align(c, 8), -1); + return 0; } @@ -228,3 +236,52 @@ int wire_compose_reply(wire_context_t *c, wire_message_t *msg, const char *signa return 0; } + +int wire_compose_error(wire_context_t *c, wire_message_t *msg, const char *error_name) +{ + + TRY_NONNULL(uint8_t*, wire_set_u8(c, msg->endianness), -1); /* endianness */ + TRY_NONNULL(uint8_t*, wire_set_u8(c, DBUS_MESSAGE_ERROR), -1); /* type */ + TRY_NONNULL(uint8_t*, wire_set_u8(c, 0), -1); /* flags */ + TRY_NONNULL(uint8_t*, wire_set_u8(c, DBUS_PROTOCOL_VERSION), -1); /* protocol_version */ + TRY_NONNULL(uint32_t*, wire_set_u32(c, 0), -1); /* body length */ + TRY_NONNULL(uint32_t*, wire_set_u32(c, msg->serial+1), -1); /* serial */ + uint32_t *header_fields_length = TRY_NONNULL(uint32_t*, wire_set_u32(c, 0), -1); /* header_fields_length */ + + uint32_t header_fields_start = c->byte_cursor; + + /* header fields */ + { + /* ERROR_NAME */ + { + /* byte (field code) */ + TRY_NONNULL(uint8_t*, wire_set_u8(c, DBUS_HEADER_FIELD_ERROR_NAME), -1); + /* variant */ + /* signature */ + TRY_NONNULL(char*, wire_set_signature(c, "s"), -1); + /* string */ + TRY_NONNULL(char*, wire_set_string(c, error_name), -1); + + // need to align to 8 byte boundry after each array element? + TRY_NONNULL(bool, wire_write_align(c, 8), -1); + } + + /* REPLY_SERIAL */ + { + /* byte (field code) */ + TRY_NONNULL(uint8_t*, wire_set_u8(c, DBUS_HEADER_FIELD_REPLY_SERIAL), -1); + /* variant */ + /* signature */ + TRY_NONNULL(char*, wire_set_signature(c, "u"), -1); + /* reply serial */ + TRY_NONNULL(uint32_t*, wire_set_u32(c, msg->serial), -1); + } + } + + *header_fields_length = c->byte_cursor - header_fields_start; + + // header ends on an 8 byte alignment + TRY_NONNULL(bool, wire_write_align(c, 8), -1); + + return 0; +} diff --git a/wire.h b/wire.h index c8a14fb..6ae35dd 100644 --- a/wire.h +++ b/wire.h @@ -8,6 +8,21 @@ #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 + enum { DBUS_HEADER_FIELD_INVALID, DBUS_HEADER_FIELD_PATH, @@ -112,5 +127,6 @@ char *wire_get_signature(wire_context_t *c, uint8_t *out_len); char *wire_set_signature(wire_context_t *c, const char *str); 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); #endif // _JITTERBUG__WIRE_H