aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--doc/protocols19
-rw-r--r--src/libmdsserver/client-list.c241
-rw-r--r--src/libmdsserver/client-list.h125
-rw-r--r--src/mds-registry.c249
-rw-r--r--src/mds-registry.h31
6 files changed, 657 insertions, 10 deletions
diff --git a/Makefile b/Makefile
index 18c810e..7b9bc0b 100644
--- a/Makefile
+++ b/Makefile
@@ -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(&reg_table, 32))
+ {
+ perror(*argv);
+ hash_table_destroy(&reg_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(&reg_table, command_key))
+ {
+ size_t address = hash_table_get(&reg_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(&reg_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(&reg_table, command_key))
+ {
+ size_t address = hash_table_get(&reg_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(&reg_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