diff options
Diffstat (limited to 'src/types')
-rw-r--r-- | src/types/filter.c | 144 | ||||
-rw-r--r-- | src/types/filter.h | 138 | ||||
-rw-r--r-- | src/types/message.c | 572 | ||||
-rw-r--r-- | src/types/message.h | 160 | ||||
-rw-r--r-- | src/types/output.c | 264 | ||||
-rw-r--r-- | src/types/output.h | 206 | ||||
-rw-r--r-- | src/types/ramps.c | 98 | ||||
-rw-r--r-- | src/types/ramps.h | 102 | ||||
-rw-r--r-- | src/types/ring.c | 224 | ||||
-rw-r--r-- | src/types/ring.h | 152 |
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 + |