Merge branch 'release/release-1.2'
[reservoir_sample] / buf.c
diff --git a/buf.c b/buf.c
new file mode 100644 (file)
index 0000000..7a94079
--- /dev/null
+++ b/buf.c
@@ -0,0 +1,243 @@
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "buf.h"
+#include "notify.h"
+#include "test_suite.h"
+
+inline
+buf_t buf_new(size_t sz) {
+       buf_t buf = malloc(sz + sizeof *buf);
+       if (buf) {
+               buf->buf_sz = sz;
+               buf->buf_start = buf->buf_used = 0;
+               memset(buf->buf, 0, sz);
+       }
+       return buf;
+}
+
+inline
+void buf_rebase(buf_t buf) {
+       if (buf->buf_start == 0)
+               return;
+       memmove(buf->buf, buf->buf + buf->buf_start, buf->buf_used);
+       buf->buf_start = 0;
+}
+
+inline
+int buf_makeroom(buf_t *pbuf, size_t roomfor) {
+       size_t new_sz;
+       void *tmp_ptr;
+
+       assert(pbuf != NULL);
+
+       if (*pbuf == NULL) {
+               *pbuf = buf_new(roomfor);
+               if (*pbuf == NULL) {
+                       return -1;
+               }
+       }
+
+       buf_rebase(*pbuf);
+
+       if (BUF_ROOM(*pbuf) >= roomfor)
+               return 0;
+
+       new_sz = (*pbuf)->buf_used + roomfor;
+       tmp_ptr = realloc(*pbuf, new_sz + sizeof **pbuf);
+       if (tmp_ptr == NULL) {
+               NOTIFY_ERROR("%s:%s", "realloc", strerror(errno));
+               return -1;
+       }
+       *pbuf = tmp_ptr;
+       (*pbuf)->buf_sz = new_sz;
+
+       return 0;
+}
+
+inline
+int buf_range_dup_or_append(buf_t src, size_t src_offset, size_t n, buf_t *pdst) {
+       assert(src != NULL);
+       assert(pdst != NULL);
+       assert(src_offset + n <= src->buf_used);
+
+       if (buf_makeroom(pdst, n)) {
+               return -1;
+       }
+
+       memcpy((*pdst)->buf + (*pdst)->buf_used, src->buf + src->buf_start + src_offset, n);
+       (*pdst)->buf_used += n;
+
+       return 0;
+}
+
+/*  Room for improvement:
+               Depending on ratio of flensed portion to buffer use, try to do less
+       copying-around of buffer ranges, by swapping buf pointers instead.
+*/
+ssize_t buf_flense(buf_t *psrc, size_t src_offset, int delimiter, buf_t *pdst) {
+       const size_t delimiter_len = 1;
+       size_t i;
+
+       assert(psrc != NULL);
+       assert(src_offset <= (*psrc)->buf_used);
+
+       NOTIFY_DEBUG("src_offset:%zu", src_offset);
+       D_BUF("src ", *psrc);
+       D_BUF("dst ", pdst ? *pdst : NULL);
+
+       for (i = src_offset; i < (*psrc)->buf_used; i++) {
+               if ((*psrc)->buf[(*psrc)->buf_start + i] == delimiter) {
+
+                       if (pdst != NULL) {
+                               if (buf_range_dup_or_append((*psrc), 0, i, pdst)) {
+                                       return -1;
+                               }
+                       }
+
+                       (*psrc)->buf_start += i + delimiter_len;
+                       (*psrc)->buf_used -= i + delimiter_len;
+
+                       D_BUF("src ", *psrc);
+                       D_BUF("dst ", pdst ? *pdst : NULL);
+                       return i + delimiter_len;
+               }
+       }
+
+       return 0;
+}
+
+#ifdef TEST
+
+static const char buf_flense_testdata1_[] = "a\nbc\ndef\nghij\nklmno\npqr\ns\ntuv\nwxyz0\n1234\n567\n89\n0leftovers";
+static const char buf_flense_testdata2_[] = "\n\nfoo\nbar\n\n";
+
+struct buf_flense_expected_result_ {
+       ssize_t r;
+       const char *buf;
+} buf_flense_expected1[] = {
+       { 1 + 1, "a" },
+       { 2 + 1, "bc" },
+       { 3 + 1, "def" },
+       { 4 + 1, "ghij" },
+       { 5 + 1, "klmno" },
+       { 3 + 1, "pqr" },
+       { 1 + 1, "s" },
+       { 3 + 1, "tuv" },
+       { 5 + 1, "wxyz0" },
+       { 4 + 1, "1234" },
+       { 3 + 1, "567" },
+       { 2 + 1, "89" },
+       { 0, "0leftovers" },
+}, buf_flense_expected2[] = {
+       { 0 + 1, "" },
+       { 0 + 1, "" },
+       { 3 + 1, "foo" },
+       { 3 + 1, "bar" },
+       { 0 + 1, "" },
+       { 0, "" },
+};
+
+struct test_buf_flense_data_ {
+       const char *src;
+       const struct buf_flense_expected_result_ *expected;
+} test_buf_flense_data1 = {
+       .src = buf_flense_testdata1_,
+       .expected = buf_flense_expected1,
+}, test_buf_flense_data2 = {
+       .src = buf_flense_testdata2_,
+       .expected = buf_flense_expected2,
+};
+
+static
+int test_buf_flense_(void *test_arg, void *suite_arg) {
+       (void)suite_arg;
+       struct test_buf_flense_data_ *test_data = (struct test_buf_flense_data_ *)test_arg;
+       const char testdata_len = strlen(test_data->src);
+       const struct buf_flense_expected_result_ *next_result = test_data->expected;
+       int retval = 0;
+       buf_t src;
+
+       TEST_INFO("initializing src buffer with %zu characters", testdata_len);
+       src = buf_new(testdata_len);
+       if (src == NULL) {
+               TEST_ERROR("%s:%s", "new_buf_", "failed");
+               return -1;
+       }
+       memcpy(src->buf, test_data->src, testdata_len);
+       src->buf_used += testdata_len;
+
+       D_BUF("src ", src);
+
+       for (;;) {
+               ssize_t r;
+               buf_t dst = NULL;
+
+               r = buf_flense(&src, 0, '\n', &dst);
+               if (r != next_result->r) {
+                       TEST_ERROR("result '%zd' does not match expected result '%zd'", r, next_result->r);
+                       retval = -1;
+               }
+               if (r == 0) {
+                       TEST_INFO("finished flensing");
+                       break;
+               }
+
+               if (strlen(next_result->buf) > dst->buf_used) {
+                       TEST_ERROR("flensed buffer smaller than expected result '%s'", next_result->buf);
+                       D_BUF("dst ", dst);
+                       retval = -1;
+               } else if (memcmp(next_result->buf, dst->buf + dst->buf_start, strlen(next_result->buf))) {
+                       TEST_ERROR("flensed buffer does not match expected result '%s'", next_result->buf);
+                       D_BUF("dst ", dst);
+                       retval = -1;
+               }
+
+               TEST_INFO("flensed: '%.*s'", (int)dst->buf_used, dst->buf + dst->buf_start);
+
+               memset(dst, 0, dst->buf_sz + sizeof *dst);
+               free(dst);
+               dst = NULL;
+
+               next_result++;
+       }
+       if (strlen(next_result->buf) > src->buf_used) {
+               TEST_ERROR("remaining buffer smaller than expected result '%s'", next_result->buf);
+               D_BUF("src ", src);
+               retval = -1;
+       } else if (memcmp(next_result->buf, src->buf + src->buf_start, strlen(next_result->buf))) {
+               TEST_ERROR("remaining buffer does not match expected result '%s'", next_result->buf);
+               D_BUF("src ", src);
+               retval = -1;
+       }
+
+       TEST_INFO("remains: '%.*s'", (int)src->buf_used, src->buf + src->buf_start);
+
+       memset(src, 0, src->buf_sz + sizeof *src);
+       free(src);
+       src = NULL;
+
+       return retval;
+}
+
+void *test_suite_data;
+
+int test_suite_pre(void *suite_data) {
+       (void)suite_data;
+       return 0;
+}
+
+int test_suite_post(void *suite_data) {
+       (void)suite_data;
+       return 0;
+}
+
+test_t test_suite[] = {
+       { "test_buf_flense_ 1", test_buf_flense_, NULL, NULL, &test_buf_flense_data1 },
+       { "test_buf_flense_ 2", test_buf_flense_, NULL, NULL, &test_buf_flense_data2 },
+       { NULL, NULL, NULL, NULL, NULL },
+};
+
+#endif /* TEST */