diff --git a/app/meson.build b/app/meson.build index 2ea3b317..8be917e9 100644 --- a/app/meson.build +++ b/app/meson.build @@ -30,6 +30,7 @@ src = [ 'src/version.c', 'src/video_buffer.c', 'src/util/acksync.c', + 'src/util/bytebuf.c', 'src/util/file.c', 'src/util/intmap.c', 'src/util/intr.c', @@ -254,6 +255,10 @@ if get_option('buildtype') == 'debug' ['test_binary', [ 'tests/test_binary.c', ]], + ['test_bytebuf', [ + 'tests/test_bytebuf.c', + 'src/util/bytebuf.c', + ]], ['test_cbuf', [ 'tests/test_cbuf.c', ]], diff --git a/app/src/util/bytebuf.c b/app/src/util/bytebuf.c new file mode 100644 index 00000000..61814376 --- /dev/null +++ b/app/src/util/bytebuf.c @@ -0,0 +1,75 @@ +#include "bytebuf.h" + +#include +#include +#include + +#include "util/log.h" + +bool +sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size) { + assert(alloc_size); + buf->data = malloc(alloc_size); + if (!buf->data) { + LOG_OOM(); + return false; + } + + buf->alloc_size = alloc_size; + buf->head = 0; + buf->tail = 0; + + return true; +} + +void +sc_bytebuf_destroy(struct sc_bytebuf *buf) { + free(buf->data); +} + +void +sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) { + assert(len); + assert(len <= sc_bytebuf_read_available(buf)); + assert(buf->tail != buf->head); // the buffer could not be empty + + size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size; + size_t right_len = right_limit - buf->tail; + if (len < right_len) { + right_len = len; + } + memcpy(to, buf->data + buf->tail, right_len); + + if (len > right_len) { + memcpy(to + right_len, buf->data, len - right_len); + } + + buf->tail = (buf->tail + len) % buf->alloc_size; +} + +void +sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) { + assert(len); + assert(len <= sc_bytebuf_read_available(buf)); + assert(buf->tail != buf->head); // the buffer could not be empty + + buf->tail = (buf->tail + len) % buf->alloc_size; +} + +void +sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) { + assert(len); + assert(len <= sc_bytebuf_write_available(buf)); + + size_t right_len = buf->alloc_size - buf->head; + if (len < right_len) { + right_len = len; + } + memcpy(buf->data + buf->head, from, right_len); + + if (len > right_len) { + memcpy(buf->data, from + right_len, len - right_len); + } + + buf->head = (buf->head + len) % buf->alloc_size; +} diff --git a/app/src/util/bytebuf.h b/app/src/util/bytebuf.h new file mode 100644 index 00000000..fcebc2d3 --- /dev/null +++ b/app/src/util/bytebuf.h @@ -0,0 +1,90 @@ +#ifndef SC_BYTEBUF_H +#define SC_BYTEBUF_H + +#include "common.h" + +#include +#include + +struct sc_bytebuf { + uint8_t *data; + // The actual capacity is (allocated - 1) so that head == tail is + // non-ambiguous + size_t alloc_size; + size_t head; // writter cursor + size_t tail; // reader cursor + // empty: tail == head + // full: ((tail + 1) % alloc_size) == head +}; + +bool +sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size); + +/** + * Copy from the bytebuf to a user-provided array + * + * The caller must check that len <= sc_bytebuf_read_available() (it is an + * error to attempt to read more bytes than available). + * + * This function is guaranteed not to write to buf->head. + */ +void +sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len); + +/** + * Drop len bytes from the buffer + * + * The caller must check that len <= sc_bytebuf_read_available() (it is an + * error to attempt to skip more bytes than available). + * + * This function is guaranteed not to write to buf->head. + * + * It is equivalent to call sc_bytebuf_read() to some array and discard the + * array (but this function is more efficient since there is no copy). + */ +void +sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len); + +/** + * Copy the user-provided array to the bytebuf + * + * The caller must check that len <= sc_bytebuf_write_available() (it is an + * error to write more bytes than the remaining available space). + * + * This function is guaranteed not to write to buf->tail. + */ +void +sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len); + +/** + * Return the number of bytes which can be read + * + * It is an error to read more bytes than available. + */ +static inline size_t +sc_bytebuf_read_available(struct sc_bytebuf *buf) { + return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size; +} + +/** + * Return the number of bytes which can be written + * + * It is an error to write more bytes than available. + */ +static inline size_t +sc_bytebuf_write_available(struct sc_bytebuf *buf) { + return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size; +} + +/** + * Return the actual capacity of the buffer (read available + write available) + */ +static inline size_t +sc_bytebuf_capacity(struct sc_bytebuf *buf) { + return buf->alloc_size - 1; +} + +void +sc_bytebuf_destroy(struct sc_bytebuf *buf); + +#endif diff --git a/app/tests/test_bytebuf.c b/app/tests/test_bytebuf.c new file mode 100644 index 00000000..fbb33765 --- /dev/null +++ b/app/tests/test_bytebuf.c @@ -0,0 +1,82 @@ +#include "common.h" + +#include +#include + +#include "util/bytebuf.h" + +void test_bytebuf_simple(void) { + struct sc_bytebuf buf; + uint8_t data[20]; + + bool ok = sc_bytebuf_init(&buf, 20); + assert(ok); + + sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1); + assert(sc_bytebuf_read_available(&buf) == 5); + + sc_bytebuf_read(&buf, data, 4); + assert(!strncmp((char *) data, "hell", 4)); + + sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1); + assert(sc_bytebuf_read_available(&buf) == 7); + + sc_bytebuf_write(&buf, (uint8_t *) "!", 1); + assert(sc_bytebuf_read_available(&buf) == 8); + + sc_bytebuf_read(&buf, &data[4], 8); + assert(sc_bytebuf_read_available(&buf) == 0); + + data[12] = '\0'; + assert(!strcmp((char *) data, "hello world!")); + assert(sc_bytebuf_read_available(&buf) == 0); + + sc_bytebuf_destroy(&buf); +} + +void test_bytebuf_boundaries(void) { + struct sc_bytebuf buf; + uint8_t data[20]; + + bool ok = sc_bytebuf_init(&buf, 20); + assert(ok); + + sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 6); + + sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 12); + + sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 18); + + sc_bytebuf_read(&buf, data, 9); + assert(!strncmp((char *) data, "hello hel", 9)); + assert(sc_bytebuf_read_available(&buf) == 9); + + sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1); + assert(sc_bytebuf_read_available(&buf) == 14); + + sc_bytebuf_write(&buf, (uint8_t *) "!", 1); + assert(sc_bytebuf_read_available(&buf) == 15); + + sc_bytebuf_skip(&buf, 3); + assert(sc_bytebuf_read_available(&buf) == 12); + + sc_bytebuf_read(&buf, data, 12); + data[12] = '\0'; + assert(!strcmp((char *) data, "hello world!")); + assert(sc_bytebuf_read_available(&buf) == 0); + + sc_bytebuf_destroy(&buf); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_bytebuf_simple(); + test_bytebuf_boundaries(); + + return 0; +}