diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | doc/protocols | 19 | ||||
-rw-r--r-- | src/libmdsserver/client-list.c | 241 | ||||
-rw-r--r-- | src/libmdsserver/client-list.h | 125 | ||||
-rw-r--r-- | src/mds-registry.c | 249 | ||||
-rw-r--r-- | src/mds-registry.h | 31 |
6 files changed, 657 insertions, 10 deletions
@@ -70,7 +70,7 @@ C_FLAGS = $(OPTIMISE) $(WARN) -std=$(STD) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) \ # Object files for the libary -LIBOBJ = linked-list hash-table fd-table mds-message util +LIBOBJ = linked-list client-list hash-table fd-table mds-message util # Servers and utilities. SERVERS = mds mds-respawn mds-server mds-echo mds-registry diff --git a/doc/protocols b/doc/protocols index 449f669..fe6ca16 100644 --- a/doc/protocols +++ b/doc/protocols @@ -77,7 +77,10 @@ Conditionally required header: Length Optional header: Action Remove availability for registry if `remove`. - Wait until listed commands are available if `wait`. + Wait until listed commands are available if `wait`, + however if a protocol becomes unavailable during this + wait period it will still be counted as available for + this wait action. Send a list of availability commands if `list`. Message: List of values for the header `Command` that you implement @@ -93,3 +96,17 @@ Reference implementation: mds-registry --------------------------------------------------------------------- +Command: reregister + Request that all servers resends `Command: register` with either + `Action: add` or without the `Action` header (does the same thing) + +Purpose: Rebuild registry created with `Command: register` if the + registry server crashes + +Compulsivity: highly recommended (infrastructure), programs may + think a protocol is not supported of the registry + server crashes if you do not implement this in your + server + +--------------------------------------------------------------------- + diff --git a/src/libmdsserver/client-list.c b/src/libmdsserver/client-list.c new file mode 100644 index 0000000..0e1a2ab --- /dev/null +++ b/src/libmdsserver/client-list.c @@ -0,0 +1,241 @@ +/** + * mds — A micro-display server + * Copyright © 2014 Mattias Andrée (maandree@member.fsf.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "client-list.h" + +#include "macros.h" + +#include <string.h> +#include <errno.h> + + +/** + * The default initial capacity + */ +#ifndef CLIENT_LIST_DEFAULT_INITIAL_CAPACITY +#define CLIENT_LIST_DEFAULT_INITIAL_CAPACITY 8 +#endif + + +/** + * Computes the nearest, but higher, power of two, + * but only if the current value is not a power of two + * + * @param value The value to be rounded up to a power of two + * @return The nearest, but not smaller, power of two + */ +static size_t to_power_of_two(size_t value) +{ + value -= 1; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; +#if __WORDSIZE == 64 + value |= value >> 32; +#endif + return value + 1; +} + + +/** + * Create a client list + * + * @param this Memory slot in which to store the new client list + * @param capacity The minimum initial capacity of the client list, 0 for default + * @return Non-zero on error, `errno` will have been set accordingly + */ +int client_list_create(client_list_t* restrict this, size_t capacity) +{ + /* Use default capacity of zero is specified. */ + if (capacity == 0) + capacity = CLIENT_LIST_DEFAULT_INITIAL_CAPACITY; + + /* Initialise the client list. */ + this->capacity = capacity = to_power_of_two(capacity); + this->size = 0; + this->clients = NULL; + if (xmalloc(this->clients, capacity, uint64_t)) + return -1; + + return 0; +} + + +/** + * Release all resources in a client list, should + * be done even if `client_list_create` fails + * + * @param this The client list + */ +void client_list_destroy(client_list_t* restrict this) +{ + free(this->clients); + this->clients = NULL; +} + + +/** + * Clone a client list + * + * @param this The client list to clone + * @param out Memory slot in which to store the new client list + * @return Non-zero on error, `errno` will have been set accordingly + */ +int client_list_clone(const client_list_t* restrict this, client_list_t* restrict out) +{ + size_t n = this->capacity * sizeof(uint64_t); + uint64_t* restrict new_clients = NULL; + + out->clients = NULL; + + if ((new_clients = malloc(n)) == NULL) + goto fail; + + out->clients = new_clients; + + out->capacity = this->capacity; + out->size = this->size; + + memcpy(out->clients, this->clients, n); + + return 0; + + fail: + free(new_clients); + return -1; +} + + +/** + * Add a client to the list + * + * @param this The list + * @param client The client to add + * @return Non-zero on error, errno will be set accordingly + */ +int client_list_add(client_list_t* restrict this, uint64_t client) +{ + if (this->size == this->capacity) + { + uint64_t* old = this->clients; + if (xrealloc(old, this->capacity <<= 1, uint64_t)) + { + this->capacity >>= 1; + this->clients = old; + return -1; + } + } + + this->clients[this->size++] = client; + return 0; +} + + +/** + * Remove a client from the list, once + * + * @param this The list + * @param client The client to remove + */ +void client_list_remove(client_list_t* restrict this, uint64_t client) +{ + size_t i; + for (i = 0; i < this->size; i++) + { + if (this->clients[i] == client) + { + size_t n = (--(this->size) - i) * sizeof(uint64_t); + memmove(this->clients + i, this->clients + i + 1, n); + + if (this->size << 1 <= this->capacity) + { + uint64_t* old = this->clients; + if (xrealloc(old, this->capacity >>= 1, uint64_t)) + { + this->capacity <<= 1; + this->clients = old; + } + } + + return; + } + } +} + + +/** + * Calculate the buffer size need to marshal a client list + * + * @param this The list + * @return The number of bytes to allocate to the output buffer + */ +size_t client_list_marshal_size(const client_list_t* restrict this) +{ + return 2 * sizeof(size_t) + this->size * sizeof(uint64_t) + sizeof(int); +} + + +/** + * Marshals a client list + * + * @param this The list + * @param data Output buffer for the marshalled data + */ +void client_list_marshal(const client_list_t* restrict this, char* restrict data) +{ + buf_set_next(data, int, CLIENT_LIST_T_VERSION); + buf_set_next(data, size_t, this->capacity); + buf_set_next(data, size_t, this->size); + + memcpy(data, this->clients, this->size * sizeof(uint64_t)); +} + + +/** + * Unmarshals a client list + * + * @param this Memory slot in which to store the new client list + * @param data In buffer with the marshalled data + * @return Non-zero on error, errno will be set accordingly. + * Destroy the list on error. + */ +int client_list_unmarshal(client_list_t* restrict this, char* restrict data) +{ + size_t n; + + /* buf_get(data, int, 0, CLIENT_LIST_T_VERSION); */ + buf_next(data, int, 1); + + this->clients = NULL; + + buf_get_next(data, size_t, this->capacity); + buf_get_next(data, size_t, this->size); + + n = this->capacity * sizeof(uint64_t); + + if ((this->clients = malloc(n)) == NULL) + return -1; + + n = this->size * sizeof(uint64_t); + + memcpy(this->clients, data, n); + + return 0; +} + diff --git a/src/libmdsserver/client-list.h b/src/libmdsserver/client-list.h new file mode 100644 index 0000000..1894b63 --- /dev/null +++ b/src/libmdsserver/client-list.h @@ -0,0 +1,125 @@ +/** + * mds — A micro-display server + * Copyright © 2014 Mattias Andrée (maandree@member.fsf.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef MDS_LIBMDSSERVER_CLIENT_LIST_H +#define MDS_LIBMDSSERVER_CLIENT_LIST_H + + +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> + + + +#define CLIENT_LIST_T_VERSION 0 + +/** + * Dynamic array of client ID:s + */ +typedef struct client_list +{ + /** + * The size of the array + */ + size_t capacity; + + /** + * The index after the last used index + */ + size_t size; + + /** + * Stored client ID:s + */ + uint64_t* clients; + +} client_list_t; + + + +/** + * Create a client list + * + * @param this Memory slot in which to store the new client list + * @param capacity The minimum initial capacity of the client list, 0 for default + * @return Non-zero on error, `errno` will have been set accordingly + */ +int client_list_create(client_list_t* restrict this, size_t capacity); + +/** + * Release all resources in a client list, should + * be done even if `client_list_create` fails + * + * @param this The client list + */ +void client_list_destroy(client_list_t* restrict this); + +/** + * Clone a client list + * + * @param this The client list to clone + * @param out Memory slot in which to store the new client list + * @return Non-zero on error, `errno` will have been set accordingly + */ +int client_list_clone(const client_list_t* restrict this, client_list_t* restrict out); + +/** + * Add a client to the list + * + * @param this The list + * @param client The client to add + * @return Non-zero on error, errno will be set accordingly + */ +int client_list_add(client_list_t* restrict this, uint64_t client); + +/** + * Remove a client from the list, once + * + * @param this The list + * @param client The client to remove + */ +void client_list_remove(client_list_t* restrict this, uint64_t client); + +/** + * Calculate the buffer size need to marshal a client list + * + * @param this The list + * @return The number of bytes to allocate to the output buffer + */ +size_t client_list_marshal_size(const client_list_t* restrict this) __attribute__((pure)); + +/** + * Marshals a client list + * + * @param this The list + * @param data Output buffer for the marshalled data + */ +void client_list_marshal(const client_list_t* restrict this, char* restrict data); + +/** + * Unmarshals a client list + * + * @param this Memory slot in which to store the new client list + * @param data In buffer with the marshalled data + * @return Non-zero on error, errno will be set accordingly. + * Destroy the list on error. + */ +int client_list_unmarshal(client_list_t* restrict this, char* restrict data); + + +#endif + diff --git a/src/mds-registry.c b/src/mds-registry.c index e0d3966..8bd7057 100644 --- a/src/mds-registry.c +++ b/src/mds-registry.c @@ -20,6 +20,9 @@ #include <libmdsserver/macros.h> #include <libmdsserver/util.h> #include <libmdsserver/mds-message.h> +#include <libmdsserver/hash-table.h> +#include <libmdsserver/hash-help.h> +#include <libmdsserver/client-list.h> #include <errno.h> #include <inttypes.h> @@ -53,7 +56,7 @@ server_characteristics_t server_characteristics = /** * Value of the ‘Message ID’ header for the next message */ -static int32_t message_id = 1; +static int32_t message_id = 2; /** * Buffer for received messages @@ -65,6 +68,11 @@ static mds_message_t received; */ static int connected = 1; +/** + * Protocol registry table + */ +static hash_table_t reg_table; + /** @@ -90,12 +98,25 @@ int initialise_server(void) const char* const message = "Command: intercept\n" "Message ID: 0\n" - "Length: 18\n" + "Length: 32\n" "\n" - "Command: register\n"; + "Command: register\n" + "Client closed\n" /* TODO support not implemented yet */ + + "Command: reregister\n" + "Message ID: 1\n" + "\n"; if (full_send(message, strlen(message))) return 1; + if (hash_table_create_tuned(®_table, 32)) + { + perror(*argv); + hash_table_destroy(®_table, NULL, NULL); + return 1; + } + reg_table.key_comparator = (compare_func*)string_comparator; + reg_table.hasher = (hash_func*)string_hash; server_initialised(); mds_message_initialise(&received); return 0; @@ -173,6 +194,8 @@ int unmarshal_server(char* state_buf) r = mds_message_unmarshal(&received, state_buf); if (r) mds_message_destroy(&received); + reg_table.key_comparator = (compare_func*)string_comparator; + reg_table.hasher = (hash_func*)string_hash; return r; } @@ -201,7 +224,7 @@ int master_loop(void) int r = mds_message_read(&received, socket_fd); if (r == 0) { - r = 0; /* TODO handle message */ + r = handle_message(); if (r == 0) continue; } @@ -225,6 +248,8 @@ int master_loop(void) connected = 1; } + /* TODO if !reexecing or failing, cleanup reg_table */ + mds_message_destroy(&received); return 0; pfail: @@ -236,6 +261,222 @@ int master_loop(void) /** + * Handle the received message + * + * @return Zero on success -1 on error or interruption, + * errno will be set accordingly + */ +int handle_message(void) +{ + const char* recv_client_id = NULL; + const char* recv_message_id = NULL; + const char* recv_length = NULL; + const char* recv_action = NULL; + size_t i, length = 0; + +#define __get_header(storage, header) \ + (startswith(received.headers[i], header)) \ + storage = received.headers[i] + strlen(header) + + for (i = 0; i < received.header_count; i++) + { + if __get_header(recv_client_id, "Client ID: "); + else if __get_header(recv_message_id, "Message ID: "); + else if __get_header(recv_length, "Length: "); + else if __get_header(recv_action, "Action: "); + else + continue; + if (recv_client_id && recv_message_id && recv_length && recv_action) + break; + } + +#undef __get_header + + + if ((recv_client_id == NULL) || (strequals(recv_client_id, "0:0"))) + { + eprint("received message from anonymous sender, ignoring."); + return 0; + } + else if (strchr(recv_client_id, ':') == NULL) + { + eprint("received message from sender without a colon it its ID, ignoring, invalid ID."); + return 0; + } + else if ((recv_length == NULL) && ((recv_action == NULL) || !strequals(recv_action, "list"))) + { + eprint("received empty message without `Action: list`, ignoring, has no effect."); + return 0; + } + else if (recv_message_id == NULL) + { + eprint("received message with ID, ignoring, master server is misbehaving."); + return 0; + } + + + if (recv_length != NULL) + length = (size_t)atoll(recv_length); + if (recv_action != NULL) + recv_action = "add"; + +#define __registry_action(action) registry_action(length, action, recv_client_id, recv_message_id) + + if (strequals(recv_action, "add")) return __registry_action(1); + else if (strequals(recv_action, "remove")) return __registry_action(-1); + else if (strequals(recv_action, "wait")) return __registry_action(0); + else if (strequals(recv_action, "list")) return list_registry(recv_client_id, recv_message_id); + else + { + eprint("received invalid action, ignoring."); + return 0; + } + +#undef __registry_action +} + + +/** + * Perform an action over the registry + * + * @param length The length of the received message + * @param action -1 to remove command, +1 to add commands, 0 to + * wait until the message commnds are registered + * @param recv_client_id The ID of the client + * @param recv_message_id The ID of the received message + * @return Zero on success -1 on error or interruption, + * errno will be set accordingly + */ +int registry_action(size_t length, int action, const char* recv_client_id, const char* recv_message_id) +{ + char* payload = received.payload; + uint64_t client = 0; + hash_table_t* wait_set = NULL; + size_t begin; + char client_words[22]; + char* client_high; + char* client_low; + + if (action) + { + strcpy(client_high = client_words, recv_client_id); + client_low = rawmemchr(client_words, ':'); + *client_low++ = '\0'; + client = (uint64_t)atoll(client_high); + client <<= 32; + client |= (uint64_t)atoll(client_low); + } + else + { + wait_set = malloc(sizeof(hash_table_t)); + if (hash_table_create(wait_set)) + { + perror(*argv); + hash_table_destroy(wait_set, NULL, NULL); + free(wait_set); + return -1; + } + wait_set->key_comparator = (compare_func*)string_comparator; + wait_set->hasher = (hash_func*)string_hash; + } + + if (received.payload_size == length) + { + if (xrealloc(received.payload, received.payload_size <<= 1, char)) + { + received.payload = payload; + received.payload_size >>= 1; + perror(*argv); + if (wait_set != NULL) + { + hash_table_destroy(wait_set, NULL, NULL); + free(wait_set); + } + return -1; + } + else + payload = received.payload; + } + + payload[length] = '\n'; + + for (begin = 0; begin < length;) + { + char* end = rawmemchr(payload + begin, '\n'); + size_t len = (size_t)(end - payload) - begin - 1; + char* command = payload + begin; + size_t command_key = (size_t)(void*)command; + + command[len] = '\0'; + begin += len + 1; + + if (action == 1) + if (hash_table_contains_key(®_table, command_key)) + { + size_t address = hash_table_get(®_table, command_key); + client_list_t* list = (client_list_t*)(void*)address; + if (client_list_add(list, client) < 0) + return -1; + } + else + { + client_list_t* list = malloc(sizeof(client_list_t)); + void* address = list; + if (list == NULL) + return perror(*argv), -1; + if (client_list_create(list, 1) || + client_list_add(list, client) || + (hash_table_put(®_table, command_key, (size_t)address) == 0)) + { + perror(*argv); + client_list_destroy(list); + free(list); + return -1; + } + } + else if ((action == -1) && hash_table_contains_key(®_table, command_key)) + { + size_t address = hash_table_get(®_table, command_key); + client_list_t* list = (client_list_t*)(void*)address; + client_list_remove(list, client); + } + else if ((action == 0) && !hash_table_contains_key(®_table, command_key)) + { + if (hash_table_put(wait_set, command_key, 1) == 0) + if (errno) + { + perror(*argv); + hash_table_destroy(wait_set, NULL, NULL); + free(wait_set); + return -1; + } + } + } + + if (action == 0) + { + /* TODO */ + } + + return 0; +} + + +/** + * Send a list of all registered commands to a client + * + * @param recv_client_id The ID of the client + * @param recv_message_id The ID of the received message + * @return Zero on success -1 on error or interruption, + * errno will be set accordingly + */ +int list_registry(const char* recv_client_id, const char* recv_message_id) +{ + /* TODO */ +} + + +/** * Send a full message even if interrupted * * @param message The message to send diff --git a/src/mds-registry.h b/src/mds-registry.h index 34a12e9..c8ced44 100644 --- a/src/mds-registry.h +++ b/src/mds-registry.h @@ -15,20 +15,43 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MDS_MDS_ECHO_H -#define MDS_MDS_ECHO_H +#ifndef MDS_MDS_REGISTRY_H +#define MDS_MDS_REGISTRY_H #include "mds-base.h" /** - * Echo the received message payload + * Handle the received message * * @return Zero on success -1 on error or interruption, * errno will be set accordingly */ -int echo_message(void); +int handle_message(void); + +/** + * Perform an action over the registry + * + * @param length The length of the received message + * @param action -1 to remove command, +1 to add commands, 0 to + * wait until the message commnds are registered + * @param recv_client_id The ID of the client + * @param recv_message_id The ID of the received message + * @return Zero on success -1 on error or interruption, + * errno will be set accordingly + */ +int registry_action(size_t length, int action, const char* recv_client_id, const char* recv_message_id); + +/** + * Send a list of all registered commands to a client + * + * @param recv_client_id The ID of the client + * @param recv_message_id The ID of the received message + * @return Zero on success -1 on error or interruption, + * errno will be set accordingly + */ +int list_registry(const char* recv_client_id, const char* recv_message_id); /** * Send a full message even if interrupted |