From cc2f34dc189074e8a93c03ebc5c0790661353b86 Mon Sep 17 00:00:00 2001 From: Richard Braun Date: Tue, 23 Apr 2019 22:50:28 +0200 Subject: mbuf: add support for variable-size message headers --- src/mbuf.c | 195 +++++++++++++++++++++++++++++++++++++++++-------------- src/mbuf.h | 48 ++++++++++++-- test/test_mbuf.c | 88 +++++++++++++++++++------ 3 files changed, 256 insertions(+), 75 deletions(-) diff --git a/src/mbuf.c b/src/mbuf.c index c7698bb..55624fb 100644 --- a/src/mbuf.c +++ b/src/mbuf.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Richard Braun. + * Copyright (c) 2018-2019 Richard Braun. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -37,38 +38,100 @@ * * The size denotes the size of the data, without the header. */ -struct mbuf_hdr { - uint32_t size; +union mbuf_hdr { + uint8_t size1; + uint16_t size2; + uint32_t size4; +#ifdef __LP64__ + uint64_t size8; +#endif /* __LP64__ */ }; -static int -mbuf_hdr_init(struct mbuf_hdr *hdr, size_t size) +static size_t +mbuf_compute_hdr_size(unsigned int order) { - hdr->size = size; + return order / CHAR_BIT; +} - if (hdr->size != size) { - return EMSGSIZE; +static void * +mbuf_hdr_get_msg_size_addr(union mbuf_hdr *hdr, unsigned int order) +{ + switch (order) { + case 8: + return &hdr->size1; + case 16: + return &hdr->size2; + default: +#ifdef __LP64__ + return &hdr->size8; + case 32: +#endif /* __LP64__ */ + return &hdr->size4; } - - return 0; } static size_t -mbuf_hdr_total_size(const struct mbuf_hdr *hdr) +mbuf_hdr_get_msg_size(const union mbuf_hdr *hdr, unsigned int order) +{ + switch (order) { + case 8: + return hdr->size1; + case 16: + return hdr->size2; + default: +#ifdef __LP64__ + return hdr->size8; + case 32: +#endif /* __LP64__ */ + return hdr->size4; + } +} + +static void +mbuf_hdr_init(union mbuf_hdr *hdr, unsigned int order, size_t size) { - return sizeof(*hdr) + hdr->size; + switch (order) { + case 8: + assert(size <= UINT8_MAX); + hdr->size1 = size; + break; + case 16: + assert(size <= UINT16_MAX); + hdr->size2 = size; + break; + default: +#ifdef __LP64__ + hdr->size8 = size; + break; + case 32: + assert(size <= UINT32_MAX); +#endif /* __LP64__ */ + hdr->size4 = size; + } } static size_t -mbuf_hdr_msg_size(const struct mbuf_hdr *hdr) +mbuf_hdr_get_total_msg_size(const union mbuf_hdr *hdr, unsigned int order) +{ + return mbuf_compute_hdr_size(order) + mbuf_hdr_get_msg_size(hdr, order); +} + +static unsigned int +mbuf_compute_order(size_t max_msg_size) { - return hdr->size; + unsigned int order; + + assert(max_msg_size != 0); + order = (sizeof(max_msg_size) * CHAR_BIT) - __builtin_clzl(max_msg_size); + return P2ROUND(order, CHAR_BIT); } void -mbuf_init(struct mbuf *mbuf, void *buf, size_t capacity) +mbuf_init(struct mbuf *mbuf, void *buf, size_t capacity, size_t max_msg_size) { cbuf_init(&mbuf->cbuf, buf, capacity); + mbuf->max_msg_size = max_msg_size; + mbuf->order = mbuf_compute_order(max_msg_size); } void @@ -80,39 +143,47 @@ mbuf_clear(struct mbuf *mbuf) static void mbuf_clear_old_msgs(struct mbuf *mbuf, size_t total_size) { - struct mbuf_hdr hdr; - size_t size; - int error __unused; + union mbuf_hdr hdr; + void *msg_size_addr; + size_t hdr_size; + + hdr_size = mbuf_compute_hdr_size(mbuf->order); + msg_size_addr = mbuf_hdr_get_msg_size_addr(&hdr, mbuf->order); do { - size = sizeof(hdr); - error = cbuf_pop(&mbuf->cbuf, &hdr, &size); + size_t msg_size, size; + int error __unused; + + size = hdr_size; + error = cbuf_pop(&mbuf->cbuf, msg_size_addr, &size); assert(!error); if (size == 0) { break; } - size = mbuf_hdr_msg_size(&hdr); + msg_size = mbuf_hdr_get_msg_size(&hdr, mbuf->order); + size = msg_size; error = cbuf_pop(&mbuf->cbuf, NULL, &size); - assert(!error && (size == mbuf_hdr_msg_size(&hdr))); + assert(!error && (size == msg_size)); } while (cbuf_avail_size(&mbuf->cbuf) < total_size); } int mbuf_push(struct mbuf *mbuf, const void *buf, size_t size, bool erase) { - struct mbuf_hdr hdr; - size_t total_size; - int error; - - error = mbuf_hdr_init(&hdr, size); + union mbuf_hdr hdr; + void *msg_size_addr; + size_t hdr_size, total_size; + int error __unused; - if (error) { - return error; + if (size > mbuf->max_msg_size) { + return EINVAL; } - total_size = mbuf_hdr_total_size(&hdr); + mbuf_hdr_init(&hdr, mbuf->order, size); + + total_size = mbuf_hdr_get_total_msg_size(&hdr, mbuf->order); if (total_size > cbuf_avail_size(&mbuf->cbuf)) { if (!erase || (total_size > cbuf_capacity(&mbuf->cbuf))) { @@ -122,7 +193,10 @@ mbuf_push(struct mbuf *mbuf, const void *buf, size_t size, bool erase) mbuf_clear_old_msgs(mbuf, total_size); } - error = cbuf_push(&mbuf->cbuf, &hdr, sizeof(hdr), erase); + hdr_size = mbuf_compute_hdr_size(mbuf->order); + msg_size_addr = mbuf_hdr_get_msg_size_addr(&hdr, mbuf->order); + + error = cbuf_push(&mbuf->cbuf, msg_size_addr, hdr_size, erase); assert(!error); error = cbuf_push(&mbuf->cbuf, buf, size, erase); assert(!error); @@ -133,39 +207,60 @@ mbuf_push(struct mbuf *mbuf, const void *buf, size_t size, bool erase) int mbuf_pop(struct mbuf *mbuf, void *buf, size_t *sizep) { - struct mbuf_hdr hdr; - size_t start, size; + size_t start; int error; start = cbuf_start(&mbuf->cbuf); + error = mbuf_read(mbuf, &start, buf, sizep); - size = sizeof(hdr); - error = cbuf_read(&mbuf->cbuf, start, &hdr, &size); - assert(!error); + if (!error) { + cbuf_set_start(&mbuf->cbuf, start); + } + + return error; +} + +int +mbuf_read(const struct mbuf *mbuf, size_t *indexp, void *buf, size_t *sizep) +{ + union mbuf_hdr hdr; + void *msg_size_addr; + size_t hdr_size, msg_size, size; + int error; + + hdr_size = mbuf_compute_hdr_size(mbuf->order); + msg_size_addr = mbuf_hdr_get_msg_size_addr(&hdr, mbuf->order); + + size = hdr_size; + error = cbuf_read(&mbuf->cbuf, *indexp, msg_size_addr, &size); - if (size == 0) { + if (error) { + return error; + } else if (size == 0) { return EAGAIN; } - assert(size == sizeof(hdr)); - size = mbuf_hdr_msg_size(&hdr); + assert(size == hdr_size); + + msg_size = mbuf_hdr_get_msg_size(&hdr, mbuf->order); - if (size > *sizep) { + if (msg_size > *sizep) { error = EMSGSIZE; goto out; } - cbuf_set_start(&mbuf->cbuf, start + sizeof(hdr)); - error = cbuf_pop(&mbuf->cbuf, buf, &size); - assert(!error && (size == mbuf_hdr_msg_size(&hdr))); + size = msg_size; + error = cbuf_read(&mbuf->cbuf, *indexp + hdr_size, buf, &size); + + if (error) { + goto out; + } + + assert(size == msg_size); + + *indexp += hdr_size + size; out: - *sizep = size; + *sizep = msg_size; return error; } - -size_t -mbuf_avail_size(const struct mbuf *mbuf) -{ - return cbuf_avail_size(&mbuf->cbuf); -} diff --git a/src/mbuf.h b/src/mbuf.h index 458b52a..a5dbc39 100644 --- a/src/mbuf.h +++ b/src/mbuf.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Richard Braun. + * Copyright (c) 2018-2019 Richard Braun. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -39,18 +39,36 @@ * * Message buffers are built on top of circular byte buffers. They provide * discrete message transfer from a producer to a consumer. + * + * The order is computed from the maximum message size, and is used to + * determine a header size that can represent up to 2^order - 1 bytes. */ struct mbuf { struct cbuf cbuf; + size_t max_msg_size; + unsigned int order; }; +static inline size_t +mbuf_start(const struct mbuf *mbuf) +{ + return cbuf_start(&mbuf->cbuf); +} + +static inline size_t +mbuf_end(const struct mbuf *mbuf) +{ + return cbuf_end(&mbuf->cbuf); +} + /* * Initialize a message buffer. * * The descriptor is set to use the given buffer for storage. Capacity * must be a power-of-two. */ -void mbuf_init(struct mbuf *mbuf, void *buf, size_t capacity); +void mbuf_init(struct mbuf *mbuf, void *buf, size_t capacity, + size_t max_msg_size); /* * Clear a message buffer. @@ -63,7 +81,8 @@ void mbuf_clear(struct mbuf *mbuf); * If the message doesn't fit in the message buffer, either because it is * larger than the capacity, or because the function isn't allowed to erase * old messages and the message buffer doesn't have enough available memory - * for the new message, EMSGSIZE is returned. + * for the new message, EMSGSIZE is returned. If the message is larger than + * the maximum message size, EINVAL is returned. */ int mbuf_push(struct mbuf *mbuf, const void *buf, size_t size, bool erase); @@ -85,8 +104,27 @@ int mbuf_push(struct mbuf *mbuf, const void *buf, size_t size, bool erase); int mbuf_pop(struct mbuf *mbuf, void *buf, size_t *sizep); /* - * Get the number of availabe bytes in a message buffer. + * Read a message from a message buffer. + * + * On entry, the indexp argument points to the index of the message to + * read in the message buffer, and the sizep argument points to the size + * of the output buffer. On return, if successful, indexp is updated + * to the index of the next message, and sizep to the size of the + * message read. + * + * If the message doesn't fit in the output buffer, it is not read, + * EMSGSIZE is returned, and the sizep argument is updated nonetheless + * to let the user know the message size, to potentially retry with a + * larger buffer. + * + * If the given index refers to the end of the buffer, then EAGAIN is + * returned. If it's outside buffer boundaries, EINVAL is returned. + * Otherwise, if it doesn't point to the beginning of a message, the + * behavior is undefined. + * + * The message buffer isn't changed by this operation. */ -size_t mbuf_avail_size(const struct mbuf *mbuf); +int mbuf_read(const struct mbuf *mbuf, size_t *indexp, + void *buf, size_t *sizep); #endif /* MBUF_H */ diff --git a/test/test_mbuf.c b/test/test_mbuf.c index 6b3f90f..3df0b01 100644 --- a/test/test_mbuf.c +++ b/test/test_mbuf.c @@ -39,7 +39,7 @@ test_regular(void) size_t size; int error; - mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf)); + mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf), 255); #define STRING "abcdef" error = mbuf_push(&mbuf, STRING, STRLEN(STRING) + 1, false); @@ -78,7 +78,7 @@ test_write_full(void) size_t size; int error; - mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf)); + mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf), (size_t)-1); #define STRING "abcdef" error = mbuf_push(&mbuf, STRING, STRLEN(STRING) + 1, false); @@ -110,7 +110,7 @@ test_overwrite(void) size_t size; int error; - mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf)); + mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf), (size_t)-1); #define STRING "abcdef" error = mbuf_push(&mbuf, STRING, STRLEN(STRING) + 1, true); @@ -135,14 +135,66 @@ test_overwrite(void) } static void -test_msg_too_big(void) +test_msg_size_power_of_two(void) +{ + char mbuf_buf[512], buffer[256]; + struct mbuf mbuf; + size_t size; + int error; + + mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf), sizeof(buffer)); + + memset(buffer, 0xab, sizeof(buffer)); + + error = mbuf_push(&mbuf, buffer, sizeof(buffer), true); + check(!error); + + memset(buffer, 0xf0, sizeof(buffer)); + + size = sizeof(buffer); + error = mbuf_pop(&mbuf, buffer, &size); + check(!error && (size == sizeof(buffer))); + + for (size_t i = 0; i < sizeof(buffer); i++) { + check(buffer[i] == (char)0xab); + } +} + +static void +test_msg_size_uint8_max(void) +{ + char mbuf_buf[512], buffer[255]; + struct mbuf mbuf; + size_t size; + int error; + + mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf), sizeof(buffer)); + + memset(buffer, 0xab, sizeof(buffer)); + + error = mbuf_push(&mbuf, buffer, sizeof(buffer), true); + check(!error); + + memset(buffer, 0xf0, sizeof(buffer)); + + size = sizeof(buffer); + error = mbuf_pop(&mbuf, buffer, &size); + check(!error && (size == sizeof(buffer))); + + for (size_t i = 0; i < sizeof(buffer); i++) { + check(buffer[i] == (char)0xab); + } +} + +static void +test_msg_too_big_to_fit(void) { char mbuf_buf[8], buffer[8]; struct mbuf mbuf; size_t size; int error; - mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf)); + mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf), (size_t)-1); #define STRING "abcdef" error = mbuf_push(&mbuf, STRING, STRLEN(STRING) + 1, true); @@ -155,25 +207,19 @@ test_msg_too_big(void) } static void -test_msg_far_too_big(void) +test_msg_bigger_than_max(void) { - char mbuf_buf[8], buffer[8]; + char mbuf_buf[512], buffer[256]; struct mbuf mbuf; size_t size; int error; - if (sizeof(size_t) <= sizeof(uint32_t)) { - fprintf(stderr, "warning: %s disabled\n", __func__); - return; - } + mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf), sizeof(buffer) - 1); - mbuf_init(&mbuf, mbuf_buf, (size_t)1 << 36); + memset(buffer, 0xab, sizeof(buffer)); -#define STRING "abcdef" - size = (size_t)1 << 32; - error = mbuf_push(&mbuf, STRING, size, true); - check(error == EMSGSIZE); -#undef STRING + error = mbuf_push(&mbuf, buffer, sizeof(buffer), true); + check(error == EINVAL); size = sizeof(buffer); error = mbuf_pop(&mbuf, buffer, &size); @@ -188,7 +234,7 @@ test_peak(void) size_t size; int error; - mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf)); + mbuf_init(&mbuf, mbuf_buf, sizeof(mbuf_buf), (size_t)-1); #define STRING "abcdef" error = mbuf_push(&mbuf, STRING, STRLEN(STRING) + 1, true); @@ -214,8 +260,10 @@ main(void) test_regular(); test_write_full(); test_overwrite(); - test_msg_too_big(); - test_msg_far_too_big(); + test_msg_size_power_of_two(); + test_msg_size_uint8_max(); + test_msg_too_big_to_fit(); + test_msg_bigger_than_max(); test_peak(); return EXIT_SUCCESS; -- cgit v1.2.3