server: Use ringbuffer for socket backlog

Currently, the socket handler uses a linear buffer for the backlog data;
this means we need to shift up to 128kB of data after each socket
write().

This change introduces a single-producer-multiple-consumer ringbuffer,
to avoid the need for memmove()ing data around; we can simply update
pointers instead of shifting data.

We add this as a new file (ringbuffer.c), to make it a little more
modular. To mitigate the risk of subtle pointer arithmetic issues, we
add a set of tests too.

Change-Id: Ib7c5151d3cf1f588436f5461000b6fed22d0681c
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
diff --git a/test/Makefile.am b/test/Makefile.am
index 45be362..e8c25d0 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -3,10 +3,15 @@
 # Run all 'check' test programs
 TESTS = $(check_PROGRAMS)
 
-#Build/add config-test to test suite
-check_PROGRAMS += config-test
-config_test_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) \
-	-DCONFIG_TEST -DSYSCONFDIR=\"\"
-config_test_LDFLAGS = -lgtest_main -lgtest $(PTHREAD_LIBS) $(OESDK_TESTCASE_FLAGS)
-config_test_SOURCES = config.c
-config_test_LDADD = $(top_builddir)/config.o
+check_PROGRAMS += \
+	test-ringbuffer-contained-read \
+	test-ringbuffer-contained-offset-read \
+	test-ringbuffer-read-commit \
+	test-ringbuffer-boundary-read \
+	test-ringbuffer-simple-poll \
+	test-ringbuffer-boundary-poll \
+	test-ringbuffer-poll-force
+
+EXTRA_DIST = ringbuffer-test-utils.c
+
+AM_CPPFLAGS = -I$(top_srcdir)
diff --git a/test/ringbuffer-test-utils.c b/test/ringbuffer-test-utils.c
new file mode 100644
index 0000000..c489f9d
--- /dev/null
+++ b/test/ringbuffer-test-utils.c
@@ -0,0 +1,97 @@
+
+struct rb_test_ctx {
+	struct ringbuffer_consumer	*rbc;
+	bool	ignore_poll;
+	bool	force_only;
+	int	count;
+	uint8_t	*data;
+	int	len;
+};
+
+void ringbuffer_test_context_init(struct rb_test_ctx *ctx)
+{
+	ctx->count = 0;
+	ctx->data = NULL;
+	ctx->len = 0;
+	ctx->ignore_poll = false;
+	ctx->force_only = false;
+}
+
+void ringbuffer_test_context_fini(struct rb_test_ctx *ctx)
+{
+	free(ctx->data);
+}
+
+enum ringbuffer_poll_ret ringbuffer_poll_nop(
+		void *data __attribute__((unused)),
+		size_t force_len __attribute__((unused)))
+{
+	return RINGBUFFER_POLL_OK;
+}
+
+enum ringbuffer_poll_ret ringbuffer_poll_append_all(void *data,
+		size_t force_len)
+{
+	struct rb_test_ctx *ctx = data;
+	size_t len, total_len;
+	uint8_t *buf;
+
+	if (ctx->ignore_poll)
+		return RINGBUFFER_POLL_OK;
+
+	if (ctx->force_only && !force_len)
+		return RINGBUFFER_POLL_OK;
+
+	ctx->count++;
+
+	total_len = 0;
+	for (;;) {
+		len = ringbuffer_dequeue_peek(ctx->rbc, total_len, &buf);
+		if (!len)
+			break;
+
+		if (ctx->force_only && total_len + len > force_len)
+			len = force_len - total_len;
+
+		ctx->data = realloc(ctx->data, ctx->len + len);
+		memcpy(ctx->data + ctx->len, buf, len);
+		ctx->len += len;
+		total_len += len;
+
+		if (ctx->force_only && total_len >= force_len)
+			break;
+	}
+	ringbuffer_dequeue_commit(ctx->rbc, total_len);
+
+	return RINGBUFFER_POLL_OK;
+}
+
+void ringbuffer_dump(struct ringbuffer *rb)
+{
+	struct ringbuffer_consumer *rbc;
+	int i, j;
+
+	printf("---- ringbuffer (%d consumer%s)\n", rb->n_consumers,
+			rb->n_consumers == 1 ? "" : "s");
+
+	for (i = 0; i < rb->size; i++) {
+		bool has_consumer = false;
+		const char *prefix = "";
+
+		if (rb->tail == i)
+			prefix = "tail=>";
+
+		printf("%6s %02x", prefix, rb->buf[i]);
+		for (j = 0; j < rb->n_consumers; j++) {
+			rbc = rb->consumers[j];
+			if (rbc->pos != i)
+				continue;
+			if (!has_consumer)
+				printf(" <=");
+			printf("c[%d],len=%zd ", j, ringbuffer_len(rbc));
+			has_consumer = true;
+		}
+		printf("\n");
+	}
+}
+
diff --git a/test/test-ringbuffer-boundary-poll.c b/test/test-ringbuffer-boundary-poll.c
new file mode 100644
index 0000000..6f6ddc7
--- /dev/null
+++ b/test/test-ringbuffer-boundary-poll.c
@@ -0,0 +1,51 @@
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "ringbuffer.c"
+#include "ringbuffer-test-utils.c"
+
+void test_boundary_poll(void)
+{
+	uint8_t in_buf[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
+	struct rb_test_ctx _ctx, *ctx = &_ctx;
+	struct ringbuffer *rb;
+	int rc;
+
+	ringbuffer_test_context_init(ctx);
+
+	rb = ringbuffer_init(10);
+
+	ctx->rbc = ringbuffer_consumer_register(rb,
+			ringbuffer_poll_append_all, ctx);
+
+	/* don't consume initial data in the poll callback */
+	ctx->ignore_poll = true;
+
+	/* queue and dequeue, so our tail is non-zero */
+	ringbuffer_queue(rb, in_buf, sizeof(in_buf));
+	ringbuffer_dequeue_commit(ctx->rbc, sizeof(in_buf));
+
+	/* start queueing data */
+	ctx->ignore_poll = false;
+
+	/* ensure we're getting the second batch of data back */
+	in_buf[0] = 'A';
+
+	rc = ringbuffer_queue(rb, in_buf, sizeof(in_buf));
+	assert(!rc);
+
+	assert(ctx->count == 1);
+	assert(ctx->len == sizeof(in_buf));
+	assert(!memcmp(in_buf, ctx->data, ctx->len));
+
+	ringbuffer_fini(rb);
+	ringbuffer_test_context_fini(ctx);
+}
+
+int main(void)
+{
+	test_boundary_poll();
+	return EXIT_SUCCESS;
+}
diff --git a/test/test-ringbuffer-boundary-read.c b/test/test-ringbuffer-boundary-read.c
new file mode 100644
index 0000000..2b982e1
--- /dev/null
+++ b/test/test-ringbuffer-boundary-read.c
@@ -0,0 +1,51 @@
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "ringbuffer.c"
+#include "ringbuffer-test-utils.c"
+
+void test_boundary_read(void)
+{
+	uint8_t *out_buf, in_buf[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
+	struct ringbuffer_consumer *rbc;
+	struct ringbuffer *rb;
+	size_t len, pos;
+	int rc;
+
+	assert(sizeof(in_buf) * 2 > 10);
+
+	rb = ringbuffer_init(10);
+	rbc = ringbuffer_consumer_register(rb, ringbuffer_poll_nop, NULL);
+
+	/* queue and dequeue, so our tail is non-zero */
+	ringbuffer_queue(rb, in_buf, sizeof(in_buf));
+	ringbuffer_dequeue_commit(rbc, sizeof(in_buf));
+
+	/* ensure we're getting the second batch of data back */
+	in_buf[0] = 'A';
+
+	/* the next queue should cross the end of the buffer */
+	rc = ringbuffer_queue(rb, in_buf, sizeof(in_buf));
+	assert(!rc);
+
+	/* dequeue everything we can */
+	pos = 0;
+	for (;;) {
+		len = ringbuffer_dequeue_peek(rbc, pos, &out_buf);
+		if (len == 0)
+			break;
+		assert(!memcmp(in_buf+pos, out_buf, len));
+		pos += len;
+	}
+	assert(pos == sizeof(in_buf));
+
+	ringbuffer_fini(rb);
+}
+
+int main(void)
+{
+	test_boundary_read();
+	return EXIT_SUCCESS;
+}
diff --git a/test/test-ringbuffer-contained-offset-read.c b/test/test-ringbuffer-contained-offset-read.c
new file mode 100644
index 0000000..5e41c6a
--- /dev/null
+++ b/test/test-ringbuffer-contained-offset-read.c
@@ -0,0 +1,38 @@
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "ringbuffer.c"
+#include "ringbuffer-test-utils.c"
+
+void test_contained_offset_read(void)
+{
+	uint8_t *out_buf, in_buf[] = { 'a', 'b', 'c' };
+	struct ringbuffer_consumer *rbc;
+	struct ringbuffer *rb;
+	size_t len;
+	int rc, i;
+
+	rb = ringbuffer_init(10);
+	rbc = ringbuffer_consumer_register(rb, ringbuffer_poll_nop, NULL);
+
+	rc = ringbuffer_queue(rb, in_buf, sizeof(in_buf));
+	assert(!rc);
+
+	/* test all possible offsets */
+	for (i = 0; i <= sizeof(in_buf); i++) {
+		len = ringbuffer_dequeue_peek(rbc, i, &out_buf);
+		assert(len == sizeof(in_buf) - i);
+		if (len)
+			assert(!memcmp(in_buf + i, out_buf, len));
+	}
+
+	ringbuffer_fini(rb);
+}
+
+int main(void)
+{
+	test_contained_offset_read();
+	return EXIT_SUCCESS;
+}
diff --git a/test/test-ringbuffer-contained-read.c b/test/test-ringbuffer-contained-read.c
new file mode 100644
index 0000000..37df3cf
--- /dev/null
+++ b/test/test-ringbuffer-contained-read.c
@@ -0,0 +1,34 @@
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "ringbuffer.c"
+#include "ringbuffer-test-utils.c"
+
+void test_contained_read(void)
+{
+	uint8_t *out_buf, in_buf[] = { 'a', 'b', 'c' };
+	struct ringbuffer_consumer *rbc;
+	struct ringbuffer *rb;
+	size_t len;
+	int rc;
+
+	rb = ringbuffer_init(10);
+	rbc = ringbuffer_consumer_register(rb, ringbuffer_poll_nop, NULL);
+
+	rc = ringbuffer_queue(rb, in_buf, sizeof(in_buf));
+	assert(!rc);
+
+	len = ringbuffer_dequeue_peek(rbc, 0, &out_buf);
+	assert(len == sizeof(in_buf));
+	assert(!memcmp(in_buf, out_buf, sizeof(in_buf)));
+
+	ringbuffer_fini(rb);
+}
+
+int main(void)
+{
+	test_contained_read();
+	return EXIT_SUCCESS;
+}
diff --git a/test/test-ringbuffer-poll-force.c b/test/test-ringbuffer-poll-force.c
new file mode 100644
index 0000000..0993de5
--- /dev/null
+++ b/test/test-ringbuffer-poll-force.c
@@ -0,0 +1,48 @@
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "ringbuffer.c"
+#include "ringbuffer-test-utils.c"
+
+void test_poll_force(void)
+{
+	uint8_t in_buf[] = { 'a', 'b', 'c', 'd', 'e', 'f', };
+	struct rb_test_ctx _ctx, *ctx = &_ctx;
+	struct ringbuffer *rb;
+	int rc;
+
+	ringbuffer_test_context_init(ctx);
+
+	rb = ringbuffer_init(5);
+
+	ctx->rbc = ringbuffer_consumer_register(rb,
+			ringbuffer_poll_append_all, ctx);
+
+	ctx->force_only = true;
+
+	/* fill the ringbuffer */
+	rc = ringbuffer_queue(rb, in_buf, 4);
+	assert(!rc);
+
+	assert(ctx->count == 0);
+
+	/* add more data */
+	rc = ringbuffer_queue(rb, in_buf + 4, 2);
+	assert(!rc);
+
+	/* we should have had a forced poll for the initial two bytes */
+	assert(ctx->count == 1);
+	assert(ctx->len == 2);
+	assert(!memcmp(in_buf, ctx->data, 2));
+
+	ringbuffer_fini(rb);
+	ringbuffer_test_context_fini(ctx);
+}
+
+int main(void)
+{
+	test_poll_force();
+	return EXIT_SUCCESS;
+}
diff --git a/test/test-ringbuffer-read-commit.c b/test/test-ringbuffer-read-commit.c
new file mode 100644
index 0000000..a4be624
--- /dev/null
+++ b/test/test-ringbuffer-read-commit.c
@@ -0,0 +1,33 @@
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "ringbuffer.c"
+#include "ringbuffer-test-utils.c"
+
+void test_read_commit(void)
+{
+	uint8_t *out_buf, in_buf[] = { 'a', 'b', 'c', };
+	struct ringbuffer_consumer *rbc;
+	struct ringbuffer *rb;
+	size_t len;
+
+	rb = ringbuffer_init(10);
+	rbc = ringbuffer_consumer_register(rb, ringbuffer_poll_nop, NULL);
+
+	ringbuffer_queue(rb, in_buf, sizeof(in_buf));
+	len = ringbuffer_dequeue_peek(rbc, 0, &out_buf);
+
+	ringbuffer_dequeue_commit(rbc, len);
+	len = ringbuffer_dequeue_peek(rbc, 0, &out_buf);
+	assert(len == 0);
+
+	ringbuffer_fini(rb);
+}
+
+int main(void)
+{
+	test_read_commit();
+	return EXIT_SUCCESS;
+}
diff --git a/test/test-ringbuffer-simple-poll.c b/test/test-ringbuffer-simple-poll.c
new file mode 100644
index 0000000..116fe8c
--- /dev/null
+++ b/test/test-ringbuffer-simple-poll.c
@@ -0,0 +1,38 @@
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "ringbuffer.c"
+#include "ringbuffer-test-utils.c"
+
+void test_simple_poll(void)
+{
+	uint8_t in_buf[] = { 'a', 'b', 'c' };
+	struct rb_test_ctx _ctx, *ctx;
+	struct ringbuffer *rb;
+	int rc;
+
+	ctx = &_ctx;
+	ringbuffer_test_context_init(ctx);
+
+	rb = ringbuffer_init(10);
+	ctx->rbc = ringbuffer_consumer_register(rb,
+			ringbuffer_poll_append_all, ctx);
+
+	rc = ringbuffer_queue(rb, in_buf, sizeof(in_buf));
+	assert(!rc);
+
+	assert(ctx->count == 1);
+	assert(ctx->len == sizeof(in_buf));
+	assert(!memcmp(in_buf, ctx->data, ctx->len));
+
+	ringbuffer_fini(rb);
+	ringbuffer_test_context_fini(ctx);
+}
+
+int main(void)
+{
+	test_simple_poll();
+	return EXIT_SUCCESS;
+}