aboutsummaryrefslogtreecommitdiffstats
path: root/src/types
diff options
context:
space:
mode:
Diffstat (limited to 'src/types')
-rw-r--r--src/types/filter.c144
-rw-r--r--src/types/filter.h138
-rw-r--r--src/types/message.c572
-rw-r--r--src/types/message.h160
-rw-r--r--src/types/output.c264
-rw-r--r--src/types/output.h206
-rw-r--r--src/types/ramps.c98
-rw-r--r--src/types/ramps.h102
-rw-r--r--src/types/ring.c224
-rw-r--r--src/types/ring.h152
10 files changed, 2060 insertions, 0 deletions
diff --git a/src/types/filter.c b/src/types/filter.c
new file mode 100644
index 0000000..e6facc9
--- /dev/null
+++ b/src/types/filter.c
@@ -0,0 +1,144 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 "filter.h"
+#include "../util.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+
+/**
+ * Free all resources allocated to a filter.
+ * The allocation of `filter` itself is not freed.
+ *
+ * @param this The filter
+ */
+void filter_destroy(struct filter* restrict this)
+{
+ free(this->class);
+ free(this->ramps);
+}
+
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal a filter
+ *
+ * @param this The filter
+ * @param buf Output buffer for the marshalled filter,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @param ramps_size The byte-size of `this->ramps`
+ * @return The number of marshalled byte
+ */
+size_t filter_marshal(const struct filter* restrict this, void* restrict buf, size_t ramps_size)
+{
+ size_t off = 0, n;
+ char nonnulls = 0;
+ char* restrict bs = buf;
+
+ if (bs != NULL)
+ {
+ if (this->class != NULL) nonnulls |= 1;
+ if (this->ramps != NULL) nonnulls |= 2;
+ *(bs + off) = nonnulls;
+ }
+ off += 1;
+
+ if (bs != NULL)
+ *(int64_t*)(bs + off) = this->priority;
+ off += sizeof(int64_t);
+
+ if (bs != NULL)
+ *(enum lifespan*)(bs + off) = this->lifespan;
+ off += sizeof(enum lifespan);
+
+ if (this->class != NULL)
+ {
+ n = strlen(this->class) + 1;
+ if (bs != NULL)
+ memcpy(bs + off, this->class, n);
+ off += n;
+ }
+
+ if (this->ramps != NULL)
+ {
+ if (bs != NULL)
+ memcpy(bs + off, this->ramps, ramps_size);
+ off += ramps_size;
+ }
+
+ return off;
+}
+
+
+/**
+ * Unmarshal a filter
+ *
+ * @param this Output for the filter
+ * @param buf Buffer with the marshalled filter
+ * @param ramps_size The byte-size of `this->ramps`
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t filter_unmarshal(struct filter* restrict this, const void* restrict buf, size_t ramps_size)
+{
+ size_t off = 0, n;
+ char nonnulls = 0;
+ const char* restrict bs = buf;
+
+ nonnulls = *(bs + off);
+ off += 1;
+
+ this->class = NULL;
+ this->ramps = NULL;
+
+ this->priority = *(const int64_t*)(bs + off);
+ off += sizeof(int64_t);
+
+ this->lifespan = *(const enum lifespan*)(bs + off);
+ off += sizeof(enum lifespan);
+
+ if (nonnulls & 1)
+ {
+ n = strlen(bs + off) + 1;
+ if (!(this->class = memdup(bs + off, n)))
+ goto fail;
+ off += n;
+ }
+
+ if (nonnulls & 2)
+ {
+ if (!(this->ramps = memdup(bs + off, ramps_size)))
+ goto fail;
+ off += ramps_size;
+ }
+
+ return off;
+
+ fail:
+ free(this->class);
+ free(this->ramps);
+ return 0;
+}
+
diff --git a/src/types/filter.h b/src/types/filter.h
new file mode 100644
index 0000000..c9e01bb
--- /dev/null
+++ b/src/types/filter.h
@@ -0,0 +1,138 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 TYPES_FILTER_H
+#define TYPES_FILTER_H
+
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+
+/**
+ * The lifespan of a filter
+ */
+enum lifespan
+{
+ /**
+ * The filter should be applied
+ * until it is explicitly removed
+ */
+ LIFESPAN_UNTIL_REMOVAL,
+
+ /**
+ * The filter should be applied
+ * until the client exists
+ */
+ LIFESPAN_UNTIL_DEATH,
+
+ /**
+ * The filter should be removed now
+ */
+ LIFESPAN_REMOVE
+
+};
+
+
+/**
+ * Information about a filter
+ */
+struct filter
+{
+ /**
+ * The client that applied it. This need not be
+ * set unless `.lifespan == LIFESPAN_UNTIL_DEATH`
+ * and unless the process itself added this.
+ * This is the file descriptor of the client's
+ * connection.
+ */
+ int client;
+
+ /**
+ * The lifespan of the filter
+ */
+ enum lifespan lifespan;
+
+ /**
+ * The priority of the filter
+ */
+ int64_t priority;
+
+ /**
+ * Identifier for the filter
+ */
+ char* class;
+
+ /**
+ * The gamma ramp adjustments for the filter.
+ * This is raw binary data. `NULL` iff
+ * `lifespan == LIFESPAN_REMOVE`.
+ */
+ void* ramps;
+
+};
+
+
+
+/**
+ * Free all resources allocated to a filter.
+ * The allocation of `filter` itself is not freed.
+ *
+ * @param this The filter
+ */
+GCC_ONLY(__attribute__((nonnull)))
+void filter_destroy(struct filter* restrict this);
+
+/**
+ * Marshal a filter
+ *
+ * @param this The filter
+ * @param buf Output buffer for the marshalled filter,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @param ramps_size The byte-size of `filter->ramps`
+ * @return The number of marshalled byte
+ */
+GCC_ONLY(__attribute__((nonnull(1))))
+size_t filter_marshal(const struct filter* restrict this, void* restrict buf, size_t ramps_size);
+
+/**
+ * Unmarshal a filter
+ *
+ * @param this Output for the filter, `.red_size`, `.green_size`,
+ * and `.blue_size` must already be set
+ * @param buf Buffer with the marshalled filter
+ * @param ramps_size The byte-size of `filter->ramps`
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+size_t filter_unmarshal(struct filter* restrict this, const void* restrict buf, size_t ramps_size);
+
+
+#endif
+
diff --git a/src/types/message.c b/src/types/message.c
new file mode 100644
index 0000000..e7c844d
--- /dev/null
+++ b/src/types/message.c
@@ -0,0 +1,572 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 "message.h"
+#include "../util.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+
+/**
+ * Initialise a message slot so that it can
+ * be used by to read messages
+ *
+ * @param this Memory slot in which to store the new message
+ * @return Non-zero on error, `errno` will be set accordingly
+ */
+int message_initialise(struct message* restrict this)
+{
+ this->headers = NULL;
+ this->header_count = 0;
+ this->payload = NULL;
+ this->payload_size = 0;
+ this->payload_ptr = 0;
+ this->buffer_size = 128;
+ this->buffer_ptr = 0;
+ this->stage = 0;
+ this->buffer = malloc(this->buffer_size);
+ if (this->buffer == NULL)
+ return -1;
+ return 0;
+}
+
+
+/**
+ * Release all resources in a message, should
+ * be done even if initialisation fails
+ *
+ * @param this The message
+ */
+void message_destroy(struct message* restrict this)
+{
+ size_t i;
+ if (this->headers != NULL)
+ for (i = 0; i < this->header_count; i++)
+ free(this->headers[i]);
+
+ free(this->headers);
+ free(this->payload);
+ free(this->buffer);
+}
+
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal a message for state serialisation
+ *
+ * @param this The message
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @return The number of marshalled byte
+ */
+size_t message_marshal(const struct message* restrict this, void* restrict buf)
+{
+ size_t i, n, off = 0;
+ char* bs = buf;
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = this->header_count;
+ off += sizeof(size_t);
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = this->payload_size;
+ off += sizeof(size_t);
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = this->payload_ptr;
+ off += sizeof(size_t);
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = this->buffer_ptr;
+ off += sizeof(size_t);
+
+ if (bs != NULL)
+ *(int*)(bs + off) = this->stage;
+ off += sizeof(int);
+
+ for (i = 0; i < this->header_count; i++)
+ {
+ n = strlen(this->headers[i]) + 1;
+ if (bs != NULL)
+ memcpy(bs + off, this->headers[i], n);
+ off += n;
+ }
+
+ if (bs != NULL)
+ memcpy(bs + off, this->payload, this->payload_ptr);
+ off += this->payload_ptr;
+
+ if (bs != NULL)
+ memcpy(bs + off, this->buffer, this->buffer_ptr);
+ off += this->buffer_ptr;
+
+ return off;
+}
+
+
+/**
+ * Unmarshal a message for state deserialisation
+ *
+ * @param this Memory slot in which to store the new message
+ * @param buf In buffer with the marshalled data
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t message_unmarshal(struct message* restrict this, const void* restrict buf)
+{
+ size_t i, n, off = 0, header_count;
+ const char* bs = buf;
+
+ this->header_count = 0;
+
+ header_count = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+
+ this->payload_size = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+
+ this->payload_ptr = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+
+ this->buffer_size = this->buffer_ptr = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+
+ this->stage = *(const int*)(bs + off);
+ off += sizeof(int);
+
+ /* Make sure that the pointers are NULL so that they are
+ not freed without being allocated when the message is
+ destroyed if this function fails. */
+ this->headers = NULL;
+ this->payload = NULL;
+ this->buffer = NULL;
+
+ /* To 2-power-multiple of 128 bytes. */
+ this->buffer_size >>= 7;
+ if (this->buffer_size == 0)
+ this->buffer_size = 1;
+ else
+ {
+ this->buffer_size -= 1;
+ this->buffer_size |= this->buffer_size >> 1;
+ this->buffer_size |= this->buffer_size >> 2;
+ this->buffer_size |= this->buffer_size >> 4;
+ this->buffer_size |= this->buffer_size >> 8;
+ this->buffer_size |= this->buffer_size >> 16;
+#if SIZE_MAX == UINT64_MAX
+ this->buffer_size |= this->buffer_size >> 32;
+#endif
+ this->buffer_size += 1;
+ }
+ this->buffer_size <<= 7;
+
+ /* Allocate header list, payload and read buffer. */
+
+ if (header_count > 0)
+ if (!(this->headers = malloc(header_count * sizeof(char*))))
+ goto fail;
+
+ if (this->payload_size > 0)
+ if (!(this->payload = malloc(this->payload_size)))
+ goto fail;
+
+ if (!(this->buffer = malloc(this->buffer_size)))
+ goto fail;
+
+ /* Fill the header list, payload and read buffer. */
+
+ for (i = 0; i < header_count; i++)
+ {
+ n = strlen(bs + off) + 1;
+ this->headers[i] = memdup(bs + off, n);
+ if (this->headers[i] == NULL)
+ goto fail;
+ off += n;
+ this->header_count++;
+ }
+
+ memcpy(this->payload, bs + off, this->payload_ptr);
+ off += this->payload_ptr;
+
+ memcpy(this->buffer, bs + off, this->buffer_ptr);
+ off += this->buffer_ptr;
+
+ return off;
+
+ fail:
+ return 0;
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+
+
+/**
+ * Extend the header list's allocation
+ *
+ * @param this The message
+ * @param extent The number of additional entries
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+static int extend_headers(struct message* restrict this, size_t extent)
+{
+ char** new;
+ if (!(new = realloc(this->headers, (this->header_count + extent) * sizeof(char*))))
+ return -1;
+ this->headers = new;
+ return 0;
+}
+
+
+/**
+ * Extend the read buffer by way of doubling
+ *
+ * @param this The message
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+static int extend_buffer(struct message* restrict this)
+{
+ char* restrict new;
+ if (!(new = realloc(this->buffer, (this->buffer_size << 1) * sizeof(char))))
+ return -1;
+ this->buffer = new;
+ this->buffer_size <<= 1;
+ return 0;
+}
+
+
+/**
+ * Reset the header list and the payload
+ *
+ * @param this The message
+ */
+GCC_ONLY(__attribute__((nonnull)))
+static void reset_message(struct message* restrict this)
+{
+ size_t i;
+ if (this->headers != NULL)
+ for (i = 0; i < this->header_count; i++)
+ free(this->headers[i]);
+ free(this->headers);
+ this->headers = NULL;
+ this->header_count = 0;
+
+ free(this->payload);
+ this->payload = NULL;
+ this->payload_size = 0;
+ this->payload_ptr = 0;
+}
+
+
+/**
+ * Read the headers the message and determine, and store, its payload's length
+ *
+ * @param this The message
+ * @return Zero on success, negative on error (malformated message: unrecoverable state)
+ */
+GCC_ONLY(__attribute__((pure, nonnull)))
+static int get_payload_length(struct message* restrict this)
+{
+ char* header;
+ size_t i;
+
+ for (i = 0; i < this->header_count; i++)
+ if (strstr(this->headers[i], "Length: ") == this->headers[i])
+ {
+ /* Store the message length. */
+ header = this->headers[i] + strlen("Length: ");
+ this->payload_size = (size_t)atol(header);
+
+ /* Do not except a length that is not correctly formated. */
+ for (; *header; header++)
+ if ((*header < '0') || ('9' < *header))
+ return -2; /* Malformated value, enters unrecoverable state. */
+
+ /* Stop searching for the ‘Length’ header, we have found and parsed it. */
+ break;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Verify that a header is correctly formatted
+ *
+ * @param header The header, must be NUL-terminated
+ * @param length The length of the header
+ * @return Zero if valid, negative if invalid (malformated message: unrecoverable state)
+ */
+GCC_ONLY(__attribute__((pure, nonnull)))
+static int validate_header(const char* restrict header, size_t length)
+{
+ char* restrict p = memchr(header, ':', length * sizeof(char));
+
+ if (verify_utf8(header, 0) < 0)
+ /* Either the string is not UTF-8, or your are under an UTF-8 attack,
+ let's just call this unrecoverable because the client will not correct. */
+ return -2;
+
+ if ((p == NULL) || /* Buck you, rawmemchr should not segfault the program. */
+ (p[1] != ' ')) /* Also an invalid format. ' ' is mandated after the ':'. */
+ return -2;
+
+ return 0;
+}
+
+
+/**
+ * Remove the beginning of the read buffer
+ *
+ * @param this The message
+ * @param length The number of characters to remove
+ * @param update_ptr Whether to update the buffer pointer
+ */
+GCC_ONLY(__attribute__((nonnull)))
+static void unbuffer_beginning(struct message* restrict this, size_t length, int update_ptr)
+{
+ memmove(this->buffer, this->buffer + length, (this->buffer_ptr - length) * sizeof(char));
+ if (update_ptr)
+ this->buffer_ptr -= length;
+}
+
+
+/**
+ * Remove the header–payload delimiter from the buffer,
+ * get the payload's size and allocate the payload
+ *
+ * @param this The message
+ * @return The return value follows the rules of `message_read`
+ */
+GCC_ONLY(__attribute__((nonnull)))
+static int initialise_payload(struct message* restrict this)
+{
+ /* Remove the \n (end of empty line) we found from the buffer. */
+ unbuffer_beginning(this, 1, 1);
+
+ /* Get the length of the payload. */
+ if (get_payload_length(this) < 0)
+ return -2; /* Malformated value, enters unrecoverable state. */
+
+ /* Allocate the payload buffer. */
+ if (this->payload_size > 0)
+ if (!(this->payload = malloc(this->payload_size)))
+ return -1;
+
+ return 0;
+}
+
+
+/**
+ * Create a header from the buffer and store it
+ *
+ * @param this The message
+ * @param length The length of the header, including LF-termination
+ * @return The return value follows the rules of `message_read`
+ */
+GCC_ONLY(__attribute__((nonnull)))
+static int store_header(struct message* restrict this, size_t length)
+{
+ char* restrict header;
+
+ /* Allocate the header. */
+ if (!(header = malloc(length))) /* Last char is a LF, which is substituted with NUL. */
+ return -1;
+ /* Copy the header data into the allocated header, */
+ memcpy(header, this->buffer, length * sizeof(char));
+ /* and NUL-terminate it. */
+ header[length - 1] = '\0';
+
+ /* Remove the header data from the read buffer. */
+ unbuffer_beginning(this, length, 1);
+
+ /* Make sure the the header syntax is correct so that
+ the program does not need to care about it. */
+ if (validate_header(header, length))
+ {
+ free(header);
+ return -2;
+ }
+
+ /* Store the header in the header list. */
+ this->headers[this->header_count++] = header;
+
+ return 0;
+}
+
+
+/**
+ * Continue reading from the socket into the buffer
+ *
+ * @param this The message
+ * @param fd The file descriptor of the socket
+ * @return The return value follows the rules of `message_read`
+ */
+GCC_ONLY(__attribute__((nonnull)))
+static int continue_read(struct message* restrict this, int fd)
+{
+ size_t n;
+ ssize_t got;
+ int r;
+
+ /* Figure out how much space we have left in the read buffer. */
+ n = this->buffer_size - this->buffer_ptr;
+
+ /* If we do not have too much left, */
+ if (n < 128)
+ {
+ /* grow the buffer, */
+ if ((r = extend_buffer(this)) < 0)
+ return r;
+
+ /* and recalculate how much space we have left. */
+ n = this->buffer_size - this->buffer_ptr;
+ }
+
+ /* Then read from the socket. */
+ errno = 0;
+ got = recv(fd, this->buffer + this->buffer_ptr, n, 0);
+ this->buffer_ptr += (size_t)(got < 0 ? 0 : got);
+ if (errno)
+ return -1;
+ if (got == 0)
+ {
+ errno = ECONNRESET;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Read the next message from a file descriptor
+ *
+ * @param this Memory slot in which to store the new message
+ * @param fd The file descriptor
+ * @return 0: At least one message is available
+ * -1: Exceptional connection:
+ * EINTR: System call interrupted
+ * EAGAIN: No message is available
+ * EWOULDBLOCK: No message is available
+ * ECONNRESET: Connection closed
+ * Other: Failure
+ * -2: Corrupt message (unrecoverable)
+ */
+GCC_ONLY(__attribute__((nonnull)))
+int message_read(struct message* restrict this, int fd)
+{
+ size_t header_commit_buffer = 0;
+ int r;
+
+ /* If we are at stage 2, we are done and it is time to start over.
+ This is important because the function could have been interrupted. */
+ if (this->stage == 2)
+ {
+ reset_message(this);
+ this->stage = 0;
+ }
+
+ /* Read from file descriptor until we have a full message. */
+ for (;;)
+ {
+ char* p;
+ size_t length;
+
+ /* Stage 0: headers. */
+ /* Read all headers that we have stored into the read buffer. */
+ while ((this->stage == 0) &&
+ ((p = memchr(this->buffer, '\n', this->buffer_ptr * sizeof(char))) != NULL))
+ if ((length = (size_t)(p - this->buffer)))
+ {
+ /* We have found a header. */
+
+ /* On every eighth header found with this function call,
+ we prepare the header list for eight more headers so
+ that it does not need to be reallocated again and again. */
+ if (header_commit_buffer == 0)
+ if ((r = extend_headers(this, header_commit_buffer = 8)) < 0)
+ return r;
+
+ /* Create and store header. */
+ if ((r = store_header(this, length + 1)) < 0)
+ return r;
+ header_commit_buffer -= 1;
+ }
+ else
+ {
+ /* We have found an empty line, i.e. the end of the headers. */
+
+ /* Remove the header–payload delimiter from the buffer,
+ get the payload's size and allocate the payload. */
+ if ((r = initialise_payload(this)) < 0)
+ return r;
+
+ /* Mark end of stage, next stage is getting the payload. */
+ this->stage = 1;
+ }
+
+
+ /* Stage 1: payload. */
+ if ((this->stage == 1) && (this->payload_size > 0))
+ {
+ /* How much of the payload that has not yet been filled. */
+ size_t need = this->payload_size - this->payload_ptr;
+ /* How much we have of that what is needed. */
+ size_t move = this->buffer_ptr < need ? this->buffer_ptr : need;
+
+ /* Copy what we have, and remove it from the the read buffer. */
+ memcpy(this->payload + this->payload_ptr, this->buffer, move * sizeof(char));
+ unbuffer_beginning(this, move, 1);
+
+ /* Keep track of how much we have read. */
+ this->payload_ptr += move;
+ }
+ if ((this->stage == 1) && (this->payload_ptr == this->payload_size))
+ {
+ /* If we have filled the payload (or there was no payload),
+ mark the end of this stage, i.e. that the message is
+ complete, and return with success. */
+ this->stage = 2;
+ return 0;
+ }
+
+
+ /* If stage 1 was not completed. */
+
+ /* Continue reading from the socket into the buffer. */
+ if ((r = continue_read(this, fd)) < 0)
+ return r;
+ }
+}
+
diff --git a/src/types/message.h b/src/types/message.h
new file mode 100644
index 0000000..00a2a50
--- /dev/null
+++ b/src/types/message.h
@@ -0,0 +1,160 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 TYPES_MESSAGE_H
+#define TYPES_MESSAGE_H
+
+
+#include <stddef.h>
+#include <limits.h>
+
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+
+/**
+ * Message passed between a server and a client
+ */
+struct message
+{
+ /**
+ * The headers in the message, each element in this list
+ * as an unparsed header, it consists of both the header
+ * name and its associated value, joined by ": ". A header
+ * cannot be `NULL` (unless its memory allocation failed,)
+ * but `headers` itself is `NULL` if there are no headers.
+ * The "Length" header should be included in this list.
+ */
+ char** restrict headers;
+
+ /**
+ * The number of headers in the message
+ */
+ size_t header_count;
+
+ /**
+ * The payload of the message, `NULL` if none (of zero-length)
+ */
+ char* restrict payload;
+
+ /**
+ * The size of the payload
+ */
+ size_t payload_size;
+
+ /**
+ * How much of the payload that has been stored (internal data)
+ */
+ size_t payload_ptr;
+
+ /**
+ * Internal buffer for the reading function (internal data)
+ */
+ char* restrict buffer;
+
+ /**
+ * The size allocated to `buffer` (internal data)
+ */
+ size_t buffer_size;
+
+ /**
+ * The number of bytes used in `buffer` (internal data)
+ */
+ size_t buffer_ptr;
+
+ /**
+ * 0 while reading headers, 1 while reading payload, and 2 when done (internal data)
+ */
+ int stage;
+
+#if INT_MAX != LONG_MAX
+ int padding__;
+#endif
+
+};
+
+
+
+/**
+ * Initialise a message slot so that it can
+ * be used by to read messages
+ *
+ * @param this Memory slot in which to store the new message
+ * @return Non-zero on error, `errno` will be set accordingly
+ */
+GCC_ONLY(__attribute__((nonnull)))
+int message_initialise(struct message* restrict this);
+
+/**
+ * Release all resources in a message, should
+ * be done even if initialisation fails
+ *
+ * @param this The message
+ */
+GCC_ONLY(__attribute__((nonnull)))
+void message_destroy(struct message* restrict this);
+
+/**
+ * Marshal a message for state serialisation
+ *
+ * @param this The message
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @return The number of marshalled byte
+ */
+GCC_ONLY(__attribute__((nonnull(1))))
+size_t message_marshal(const struct message* restrict this, void* restrict buf);
+
+/**
+ * Unmarshal a message for state deserialisation
+ *
+ * @param this Memory slot in which to store the new message
+ * @param buf In buffer with the marshalled data
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+size_t message_unmarshal(struct message* restrict this, const void* restrict buf);
+
+/**
+ * Read the next message from a file descriptor
+ *
+ * @param this Memory slot in which to store the new message
+ * @param fd The file descriptor
+ * @return 0: At least one message is available
+ * -1: Exceptional connection:
+ * EINTR: System call interrupted
+ * EAGAIN: No message is available
+ * EWOULDBLOCK: No message is available
+ * ECONNRESET: Connection closed
+ * Other: Failure
+ * -2: Corrupt message (unrecoverable)
+ */
+GCC_ONLY(__attribute__((nonnull)))
+int message_read(struct message* restrict this, int fd);
+
+
+#endif
+
diff --git a/src/types/output.c b/src/types/output.c
new file mode 100644
index 0000000..8576baa
--- /dev/null
+++ b/src/types/output.c
@@ -0,0 +1,264 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 "output.h"
+#include "../util.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+
+/**
+ * Free all resources allocated to an output.
+ * The allocation of `output` itself is not freed,
+ * nor is its the libgamma destroyed.
+ *
+ * @param this The output
+ */
+void output_destroy(struct output* restrict this)
+{
+ size_t i;
+
+ if (this->supported != LIBGAMMA_NO)
+ switch (this->depth)
+ {
+ case 8:
+ libgamma_gamma_ramps8_destroy(&(this->saved_ramps.u8));
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_ramps8_destroy(&(this->table_sums[i].u8));
+ break;
+ case 16:
+ libgamma_gamma_ramps16_destroy(&(this->saved_ramps.u16));
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_ramps16_destroy(&(this->table_sums[i].u16));
+ break;
+ case 32:
+ libgamma_gamma_ramps32_destroy(&(this->saved_ramps.u32));
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_ramps32_destroy(&(this->table_sums[i].u32));
+ break;
+ case 64:
+ libgamma_gamma_ramps64_destroy(&(this->saved_ramps.u64));
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_ramps64_destroy(&(this->table_sums[i].u64));
+ break;
+ case -1:
+ libgamma_gamma_rampsf_destroy(&(this->saved_ramps.f));
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_rampsf_destroy(&(this->table_sums[i].f));
+ break;
+ case -2:
+ libgamma_gamma_rampsd_destroy(&(this->saved_ramps.d));
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_rampsd_destroy(&(this->table_sums[i].d));
+ break;
+ default:
+ break; /* impossible */
+ }
+
+ for (i = 0; i < this->table_size; i++)
+ filter_destroy(this->table_filters + i);
+
+ free(this->table_filters);
+ free(this->table_sums);
+ free(this->name);
+}
+
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal an output
+ *
+ * @param this The output
+ * @param buf Output buffer for the marshalled output,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @return The number of marshalled byte
+ */
+size_t output_marshal(const struct output* restrict this, void* restrict buf)
+{
+ size_t off = 0, i, n;
+ char* bs = buf;
+
+ if (bs != NULL)
+ *(signed*)(bs + off) = this->depth;
+ off += sizeof(signed);
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = this->red_size;
+ off += sizeof(size_t);
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = this->green_size;
+ off += sizeof(size_t);
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = this->blue_size;
+ off += sizeof(size_t);
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = this->ramps_size;
+ off += sizeof(size_t);
+
+ if (bs != NULL)
+ *(enum libgamma_decision*)(bs + off) = this->supported;
+ off += sizeof(enum libgamma_decision);
+
+ n = strlen(this->name) + 1;
+ if (bs != NULL)
+ memcpy(bs + off, this->name, n);
+ off += n;
+
+ off += gamma_ramps_marshal(&(this->saved_ramps), bs ? bs + off : NULL, this->ramps_size);
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = this->table_size;
+ off += sizeof(size_t);
+
+ for (i = 0; i < this->table_size; i++)
+ {
+ off += filter_marshal(this->table_filters + i, bs ? bs + off : NULL, this->ramps_size);
+ off += gamma_ramps_marshal(this->table_sums + i, bs ? bs + off : NULL, this->ramps_size);
+ }
+
+ return off;
+}
+
+
+/**
+ * Unmarshal an output
+ *
+ * @param this Output for the output
+ * @param buf Buffer with the marshalled output
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t output_unmarshal(struct output* restrict this, const void* restrict buf)
+{
+ size_t off = 0, i, n;
+ const char* bs = buf;
+
+ this->crtc = NULL;
+ this->name = NULL;
+
+ this->depth = *(const signed*)(bs + off);
+ off += sizeof(signed);
+
+ this->red_size = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+
+ this->green_size = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+
+ this->blue_size = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+
+ this->ramps_size = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+
+ this->supported = *(const enum libgamma_decision*)(bs + off);
+ off += sizeof(enum libgamma_decision);
+
+ n = strlen(bs + off) + 1;
+ this->name = memdup(bs + off, n);
+ if (this->name == NULL)
+ return 0;
+
+ off += n = gamma_ramps_unmarshal(&(this->saved_ramps), bs, this->ramps_size);
+ COPY_RAMP_SIZES(&(this->saved_ramps.u8), this);
+ if (n == 0)
+ return 0;
+
+ this->table_size = this->table_alloc = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+ if (this->table_size > 0)
+ {
+ this->table_filters = calloc(this->table_size, sizeof(*(this->table_filters)));
+ if (this->table_filters == NULL)
+ return 0;
+ this->table_sums = calloc(this->table_size, sizeof(*(this->table_sums)));
+ if (this->table_sums == NULL)
+ return 0;
+ }
+
+ for (i = 0; i < this->table_size; i++)
+ {
+ off += n = filter_unmarshal(this->table_filters + i, bs + off, this->ramps_size);
+ if (n == 0)
+ return 0;
+ COPY_RAMP_SIZES(&(this->table_sums[i].u8), this);
+ off += n = gamma_ramps_unmarshal(this->table_sums + i, bs + off, this->ramps_size);
+ if (n == 0)
+ return 0;
+ }
+
+ return off;
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+
+
+/**
+ * Compare to outputs by the names of their respective CRTC:s
+ *
+ * @param a Return -1 if this one is lower
+ * @param b Return +1 if this one is higher
+ * @return See description of `a` and `b`,
+ * 0 if returned if they are the same
+ */
+int output_cmp_by_name(const void* restrict a, const void* restrict b)
+{
+ const char* an = ((const struct output*)a)->name;
+ const char* bn = ((const struct output*)b)->name;
+ return strcmp(an, bn);
+}
+
+
+/**
+ * Find an output by its name
+ *
+ * @param key The name of the output
+ * @param base The array of outputs
+ * @param n The number of elements in `base`
+ * @return Output find in `base`, `NULL` if not found
+ */
+struct output* output_find_by_name(const char* restrict key, struct output* restrict base, size_t n)
+{
+ struct output k;
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+ k.name = (char*)key;
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ return bsearch(&k, base, n, sizeof(*base), output_cmp_by_name);
+}
+
diff --git a/src/types/output.h b/src/types/output.h
new file mode 100644
index 0000000..f8a4c3e
--- /dev/null
+++ b/src/types/output.h
@@ -0,0 +1,206 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 TYPES_OUTPUT_H
+#define TYPES_OUTPUT_H
+
+
+#include <stddef.h>
+
+#include <libgamma.h>
+
+#include "ramps.h"
+#include "filter.h"
+
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+
+/**
+ * Copy the ramp sizes
+ *
+ * This macro supports both `struct output`
+ * and `struct gamma_ramps`
+ *
+ * @param dest The destination
+ * @param src The source
+ */
+#define COPY_RAMP_SIZES(dest, src) \
+ ((dest)->red_size = (src)->red_size, \
+ (dest)->green_size = (src)->green_size, \
+ (dest)->blue_size = (src)->blue_size)
+
+
+
+/**
+ * Information about an output
+ */
+struct output
+{
+ /**
+ * -2: double
+ * -1: float
+ * 8: uint8_t
+ * 16: uint16_t
+ * 32: uint32_t
+ * 64: uint64_t
+ */
+ signed depth;
+
+ /**
+ * Whether gamma ramps are supported
+ */
+ enum libgamma_decision supported;
+
+ /**
+ * The number of stops in the red gamma ramp
+ */
+ size_t red_size;
+
+ /**
+ * The number of stops in the green gamma ramp
+ */
+ size_t green_size;
+
+ /**
+ * The number of stops in the blue gamma ramp
+ */
+ size_t blue_size;
+
+ /**
+ * `.red_size + .green_size + .blue_size`
+ * multiplied by the byte-size of each stop
+ */
+ size_t ramps_size;
+
+ /**
+ * The name of the output, will be its EDID
+ * if available, otherwise it will be the
+ * index of the partition, followed by a dot
+ * and the index of the CRTC within the
+ * partition, or if a name for the connector
+ * is available: the index of the partition
+ * followed by a dot and the name of the
+ * connector
+ */
+ char* restrict name;
+
+ /**
+ * The libgamma state for the output
+ */
+ libgamma_crtc_state_t* restrict crtc;
+
+ /**
+ * Saved gamma ramps
+ */
+ union gamma_ramps saved_ramps;
+
+ /**
+ * The table of all applied filters
+ */
+ struct filter* restrict table_filters;
+
+ /**
+ * `.table_sums[i]` is the resulting
+ * adjustment made when all filter
+ * from `.table_filters[0]` up to and
+ * including `.table_filters[i]` has
+ * been applied
+ */
+ union gamma_ramps* restrict table_sums;
+
+ /**
+ * The number of elements allocated
+ * for `.table_filters` and for `.table_sums`
+ */
+ size_t table_alloc;
+
+ /**
+ * The number of elements stored in
+ * `.table_filters` and in `.table_sums`
+ */
+ size_t table_size;
+
+};
+
+
+
+/**
+ * Free all resources allocated to an output.
+ * The allocation of `output` itself is not freed,
+ * nor is its the libgamma destroyed.
+ *
+ * @param this The output
+ */
+GCC_ONLY(__attribute__((nonnull)))
+void output_destroy(struct output* restrict this);
+
+/**
+ * Marshal an output
+ *
+ * @param this The output
+ * @param buf Output buffer for the marshalled output,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @return The number of marshalled byte
+ */
+GCC_ONLY(__attribute__((nonnull(1))))
+size_t output_marshal(const struct output* restrict this, void* restrict buf);
+
+/**
+ * Unmarshal an output
+ *
+ * @param this Output for the output
+ * @param buf Buffer with the marshalled output
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+size_t output_unmarshal(struct output* restrict this, const void* restrict buf);
+
+/**
+ * Compare to outputs by the names of their respective CRTC:s
+ *
+ * @param a Return -1 if this one is lower
+ * @param b Return +1 if this one is higher
+ * @return See description of `a` and `b`,
+ * 0 if returned if they are the same
+ */
+GCC_ONLY(__attribute__((pure, nonnull)))
+int output_cmp_by_name(const void* restrict a, const void* restrict b);
+
+/**
+ * Find an output by its name
+ *
+ * @param key The name of the output
+ * @param base The array of outputs
+ * @param n The number of elements in `base`
+ * @return Output find in `base`, `NULL` if not found
+ */
+GCC_ONLY(__attribute__((pure, nonnull)))
+struct output* output_find_by_name(const char* restrict key, struct output* restrict base, size_t n);
+
+
+#endif
+
diff --git a/src/types/ramps.c b/src/types/ramps.c
new file mode 100644
index 0000000..30bed3e
--- /dev/null
+++ b/src/types/ramps.c
@@ -0,0 +1,98 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 "ramps.h"
+
+#include <libclut.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+
+/**
+ * The name of the process
+ */
+extern char* restrict argv0;
+
+
+
+/**
+ * Marshal a ramp trio
+ *
+ * @param this The ramps
+ * @param buf Output buffer for the marshalled ramps,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @param ramps_size The byte-size of ramps
+ * @return The number of marshalled byte
+ */
+size_t gamma_ramps_marshal(const union gamma_ramps* restrict this, void* restrict buf, size_t ramps_size)
+{
+ if (buf != NULL)
+ memcpy(buf, this->u8.red, ramps_size);
+ return ramps_size;
+}
+
+
+/**
+ * Unmarshal a ramp trio
+ *
+ * @param this Output for the ramps, `.red_size`, `.green_size`,
+ * and `.blue_size` must already be set
+ * @param buf Buffer with the marshalled ramps
+ * @param ramps_size The byte-size of ramps
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t gamma_ramps_unmarshal(union gamma_ramps* restrict this, const void* restrict buf, size_t ramps_size)
+{
+ size_t depth = ramps_size / (this->u8.red_size + this->u8.green_size + this->u8.blue_size);
+ int r = 0;
+ switch (depth)
+ {
+ case 1:
+ r = libgamma_gamma_ramps8_initialise(&(this->u8));
+ break;
+ case 2:
+ r = libgamma_gamma_ramps16_initialise(&(this->u16));
+ break;
+ case 4:
+ r = libgamma_gamma_ramps32_initialise(&(this->u32));
+ break;
+ case 8:
+ r = libgamma_gamma_ramps64_initialise(&(this->u64));
+ break;
+ default:
+ if (depth == sizeof(float))
+ r = libgamma_gamma_rampsf_initialise(&(this->f));
+ else if (depth == sizeof(double))
+ r = libgamma_gamma_rampsd_initialise(&(this->d));
+ else
+ abort();
+ break;
+ }
+ if (r)
+ {
+ libgamma_perror(argv0, r);
+ errno = 0;
+ return 0;
+ }
+ memcpy(this->u8.red, buf, ramps_size);
+ return ramps_size;
+}
+
diff --git a/src/types/ramps.h b/src/types/ramps.h
new file mode 100644
index 0000000..001d504
--- /dev/null
+++ b/src/types/ramps.h
@@ -0,0 +1,102 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 TYPES_RAMPS_H
+#define TYPES_RAMPS_H
+
+
+#include <libgamma.h>
+
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+
+/**
+ * Gamma ramps union for all
+ * lbigamma gamma ramps types
+ */
+union gamma_ramps
+{
+ /**
+ * Ramps with 8-bit value
+ */
+ libgamma_gamma_ramps8_t u8;
+
+ /**
+ * Ramps with 16-bit value
+ */
+ libgamma_gamma_ramps16_t u16;
+
+ /**
+ * Ramps with 32-bit value
+ */
+ libgamma_gamma_ramps32_t u32;
+
+ /**
+ * Ramps with 64-bit value
+ */
+ libgamma_gamma_ramps64_t u64;
+
+ /**
+ * Ramps with `float` value
+ */
+ libgamma_gamma_rampsf_t f;
+
+ /**
+ * Ramps with `double` value
+ */
+ libgamma_gamma_rampsd_t d;
+
+};
+
+
+
+/**
+ * Marshal a ramp trio
+ *
+ * @param this The ramps
+ * @param buf Output buffer for the marshalled ramps,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @param ramps_size The byte-size of ramps
+ * @return The number of marshalled byte
+ */
+GCC_ONLY(__attribute__((nonnull(1))))
+size_t gamma_ramps_marshal(const union gamma_ramps* restrict this, void* restrict buf, size_t ramps_size);
+
+/**
+ * Unmarshal a ramp trio
+ *
+ * @param this Output for the ramps
+ * @param buf Buffer with the marshalled ramps
+ * @param ramps_size The byte-size of ramps
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+size_t gamma_ramps_unmarshal(union gamma_ramps* restrict this, const void* restrict buf, size_t ramps_size);
+
+
+#endif
+
diff --git a/src/types/ring.c b/src/types/ring.c
new file mode 100644
index 0000000..13cf8c9
--- /dev/null
+++ b/src/types/ring.c
@@ -0,0 +1,224 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 "ring.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+
+/**
+ * Initialise a ring buffer
+ *
+ * @param this The ring buffer
+ */
+void ring_initialise(struct ring* restrict this)
+{
+ this->start = 0;
+ this->end = 0;
+ this->size = 0;
+ this->buffer = NULL;
+}
+
+
+/**
+ * Release resource allocated to a ring buffer
+ *
+ * @param this The ring buffer
+ */
+void ring_destroy(struct ring* restrict this)
+{
+ free(this->buffer);
+}
+
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal a ring buffer
+ *
+ * @param this The ring buffer
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` to only measure how large buffer
+ * is needed
+ * @return The number of marshalled bytes
+ */
+size_t ring_marshal(const struct ring* restrict this, void* restrict buf)
+{
+ size_t off = 0, n = this->end - this->start;
+ char* bs = buf;
+
+ if (bs != NULL)
+ *(size_t*)(bs + off) = n;
+ off += sizeof(size_t);
+
+ if (bs != NULL)
+ memcpy(bs + off, this->buffer + this->start, n);
+ off += n;
+
+ return off;
+}
+
+
+/**
+ * Unmarshal a ring buffer
+ *
+ * @param this Output parameter for the ring buffer
+ * @param buf Buffer with the marshalled data
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t ring_unmarshal(struct ring* restrict this, const void* restrict buf)
+{
+ size_t off = 0;
+ const char* bs = buf;
+
+ ring_initialise(this);
+
+ this->size = this->end = *(const size_t*)(bs + off);
+ off += sizeof(size_t);
+
+ if (this->end > 0)
+ {
+ if (!(this->buffer = malloc(this->end)))
+ return 0;
+
+ memcpy(this->buffer, bs + off, this->end);
+ off += this->end;
+ }
+
+ return off;
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+
+
+/**
+ * Append data to a ring buffer
+ *
+ * @param this The ring buffer
+ * @param data The new data
+ * @param n The number of bytes in `data`
+ * @return Zero on success, -1 on error
+ */
+int ring_push(struct ring* restrict this, void* restrict data, size_t n)
+{
+ size_t used = 0;
+
+ if (this->start == this->end)
+ {
+ if (this->buffer != NULL)
+ used = this->size;
+ }
+ else if (this->start > this->end)
+ used = this->size - this->start + this->end;
+ else
+ used = this->start - this->end;
+
+ if (used + n > this->size)
+ {
+ char* restrict new = malloc(used + n);
+ if (new == NULL)
+ return -1;
+ if (this->buffer)
+ {
+ if (this->start < this->end)
+ memcpy(new, this->buffer + this->start, this->end - this->start);
+ else
+ {
+ memcpy(new, this->buffer + this->start, this->size - this->start);
+ memcpy(new + this->size - this->start, this->buffer, this->end);
+ }
+ }
+ memcpy(new + used, data, n);
+ this->buffer = new;
+ this->start = 0;
+ this->end = used + n;
+ this->size = used + n;
+ }
+ else if ((this->start >= this->end) || (this->end + n <= this->size))
+ {
+ memcpy(this->buffer + this->end, data, n);
+ this->end += n;
+ }
+ else
+ {
+ memcpy(this->buffer + this->end, data, this->size - this->end);
+ data = (char*)data + (this->size - this->end);
+ n -= this->size - this->end;
+ memcpy(this->buffer, data, n);
+ this->end = n;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Get queued data from a ring buffer
+ *
+ * It can take up to two calls (with `ring_pop` between)
+ * to get all queued data
+ *
+ * @param this The ring buffer
+ * @param n Output parameter for the length
+ * of the returned segment
+ * @return The beginning of the queued data,
+ * `NULL` if there is nothing more
+ */
+void* ring_peek(struct ring* restrict this, size_t* restrict n)
+{
+ if (this->buffer == NULL)
+ return *n = 0, NULL;
+
+ if (this->start < this->end)
+ *n = this->end - this->start;
+ else
+ *n = this->size - this->start;
+ return this->buffer + this->start;
+}
+
+
+/**
+ * Dequeue data from a ring bubber
+ *
+ * @param this The ring buffer
+ * @param n The number of bytes to dequeue
+ */
+void ring_pop(struct ring* restrict this, size_t n)
+{
+ this->start += n;
+ this->start %= this->size;
+ if (this->start == this->end)
+ {
+ free(this->buffer);
+ this->start = 0;
+ this->end = 0;
+ this->size = 0;
+ this->buffer = NULL;
+ }
+}
+
diff --git a/src/types/ring.h b/src/types/ring.h
new file mode 100644
index 0000000..0474f39
--- /dev/null
+++ b/src/types/ring.h
@@ -0,0 +1,152 @@
+/**
+ * coopgammad -- Cooperative gamma server
+ * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
+ *
+ * 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 TYPES_RING_H
+#define TYPES_RING_H
+
+
+#include <stddef.h>
+
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+
+/**
+ * Ring buffer
+ */
+struct ring
+{
+ /**
+ * Buffer for the data
+ */
+ char* restrict buffer;
+
+ /**
+ * The first set byte in `.buffer`
+ */
+ size_t start;
+
+ /**
+ * The last set byte in `.buffer`, plus 1
+ */
+ size_t end;
+
+ /**
+ * The size of `.buffer`
+ */
+ size_t size;
+};
+
+
+
+/**
+ * Initialise a ring buffer
+ *
+ * @param this The ring buffer
+ */
+GCC_ONLY(__attribute__((nonnull)))
+void ring_initialise(struct ring* restrict this);
+
+/**
+ * Release resource allocated to a ring buffer
+ *
+ * @param this The ring buffer
+ */
+GCC_ONLY(__attribute__((nonnull)))
+void ring_destroy(struct ring* restrict this);
+
+/**
+ * Marshal a ring buffer
+ *
+ * @param this The ring buffer
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` to only measure how large buffer
+ * is needed
+ * @return The number of marshalled bytes
+ */
+GCC_ONLY(__attribute__((nonnull(1))))
+size_t ring_marshal(const struct ring* restrict this, void* restrict buf);
+
+/**
+ * Unmarshal a ring buffer
+ *
+ * @param this Output parameter for the ring buffer
+ * @param buf Buffer with the marshalled data
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((nonnull)))
+size_t ring_unmarshal(struct ring* restrict this, const void* restrict buf);
+
+/**
+ * Append data to a ring buffer
+ *
+ * @param this The ring buffer
+ * @param data The new data
+ * @param n The number of bytes in `data`
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((nonnull(1))))
+int ring_push(struct ring* restrict this, void* restrict data, size_t n);
+
+/**
+ * Get queued data from a ring buffer
+ *
+ * It can take up to two calls (with `ring_pop` between)
+ * to get all queued data
+ *
+ * @param this The ring buffer
+ * @param n Output parameter for the length
+ * of the returned segment
+ * @return The beginning of the queued data,
+ * `NULL` if there is nothing more
+ */
+GCC_ONLY(__attribute__((nonnull)))
+void* ring_peek(struct ring* restrict this, size_t* restrict n);
+
+/**
+ * Dequeue data from a ring bubber
+ *
+ * @param this The ring buffer
+ * @param n The number of bytes to dequeue
+ */
+GCC_ONLY(__attribute__((nonnull)))
+void ring_pop(struct ring* restrict this, size_t n);
+
+/**
+ * Check whether there is more data waiting
+ * in a ring buffer
+ *
+ * @param this The ring buffer
+ * @return 1 if there is more data, 0 otherwise
+ */
+GCC_ONLY(__attribute__((nonnull)))
+static inline int ring_have_more(struct ring* restrict this)
+{
+ return this->buffer != NULL;
+}
+
+
+#endif
+