Browse Source

pushpull: new module.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 11 years ago
parent
commit
17aa322abd

+ 1 - 0
Makefile-ccan

@@ -80,6 +80,7 @@ MODS_WITH_SRC := antithread \
 	ogg_to_pcm \
 	opt \
 	ptr_valid \
+	pushpull \
 	rbtree \
 	rbuf \
 	read_write_all \

+ 1 - 0
ccan/pushpull/LICENSE

@@ -0,0 +1 @@
+../../licenses/CC0

+ 76 - 0
ccan/pushpull/_info

@@ -0,0 +1,76 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * pushpull - simple marshalling/unmarshalling routines
+ *
+ * This code lets you clearly add simple types into a buffer (the push
+ * functions) and remove them (the pull functions).  The buffer stores
+ * the values as little-endian for machine portability.  The pull functions
+ * don't need to be checked on every call, but error state is kept so you
+ * can check if there was an error at the end.
+ *
+ * The normal way to use this is to create your own higher-level marshal
+ * and unmarshal functions in terms of these.
+ *
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ * License: CC0 (Public domain)
+ *
+ * Example:
+ *	#include <ccan/pushpull/push.h>
+ *	#include <ccan/pushpull/pull.h>
+ *	#include <ccan/err/err.h>
+ *	#include <string.h>
+ *	#include <stdio.h>
+ *	#include <unistd.h>
+ *
+ *	int main(int argc, char *argv[])
+ *	{
+ *		if (argv[1] && !strcmp(argv[1], "push")) {
+ *			int i;
+ *			char *buf = malloc(1);
+ *			size_t len = 0;
+ *
+ *			// We ignore allocation failure!
+ *			for (i = 2; i < argc; i++)
+ *				push_u32(&buf, &len, atol(argv[i]));
+ *
+ *			write(STDOUT_FILENO, buf, len);
+ *		} else if (argc == 2 && !strcmp(argv[1], "pull")) {
+ *			int r, max = 32;
+ *			size_t len = 0;
+ *			char *buf = malloc(max);
+ *			const char *p;
+ *			uint32_t val;
+ *
+ *			while ((r = read(STDIN_FILENO, buf+len, max-len)) > 0) {
+ *				len += r;
+ *				if (len == max) {
+ *					max *= 2;
+ *					// We crash on allocation failure
+ *					buf = realloc(buf, max);
+ *				}
+ *			}
+ *
+ *			p = buf;
+ *			while (pull_u32(&p, &len, &val))
+ *				printf("%u ", val);
+ *		} else
+ *			errx(1, "Usage: %s [push|pull] [<number>...]", argv[0]);
+ *		return 0;
+ *	}
+ */
+int main(int argc, char *argv[])
+{
+	/* Expect exactly one argument */
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0) {
+		printf("ccan/endian\n");
+		return 0;
+	}
+
+	return 1;
+}

+ 105 - 0
ccan/pushpull/pull.c

@@ -0,0 +1,105 @@
+/* CC0 license (public domain) - see LICENSE file for details */
+#include "pull.h"
+#include <ccan/endian/endian.h>
+#include <string.h>
+
+bool pull_bytes(const char **p, size_t *max_len, void *dst, size_t len)
+{
+	if (*max_len < len) {
+		*p = NULL;
+		*max_len = 0;
+		return false;
+	}
+	if (dst)
+		memcpy(dst, *p, len);
+	*max_len -= len;
+	*p += len;
+	return true;
+}
+
+bool pull_u64(const char **p, size_t *max_len, uint64_t *val)
+{
+	leint64_t v;
+
+	if (pull_bytes(p, max_len, &v, sizeof(v))) {
+		if (val)
+			*val = le64_to_cpu(v);
+		return true;
+	}
+	return false;
+}
+
+bool pull_u32(const char **p, size_t *max_len, uint32_t *val)
+{
+	leint32_t v;
+
+	if (pull_bytes(p, max_len, &v, sizeof(v))) {
+		if (val)
+			*val = le32_to_cpu(v);
+		return true;
+	}
+	return false;
+}
+
+bool pull_u16(const char **p, size_t *max_len, uint16_t *val)
+{
+	leint16_t v;
+
+	if (pull_bytes(p, max_len, &v, sizeof(v))) {
+		if (val)
+			*val = le16_to_cpu(v);
+		return true;
+	}
+	return false;
+}
+
+bool pull_u8(const char **p, size_t *max_len, uint8_t *val)
+{
+	return pull_bytes(p, max_len, val, sizeof(*val));
+}
+
+bool pull_s64(const char **p, size_t *max_len, int64_t *val)
+{
+	leint64_t v;
+
+	if (pull_bytes(p, max_len, &v, sizeof(v))) {
+		if (val)
+			*val = le64_to_cpu(v);
+		return true;
+	}
+	return false;
+}
+
+bool pull_s32(const char **p, size_t *max_len, int32_t *val)
+{
+	leint32_t v;
+
+	if (pull_bytes(p, max_len, &v, sizeof(v))) {
+		if (val)
+			*val = le32_to_cpu(v);
+		return true;
+	}
+	return false;
+}
+
+bool pull_s16(const char **p, size_t *max_len, int16_t *val)
+{
+	leint16_t v;
+
+	if (pull_bytes(p, max_len, &v, sizeof(v))) {
+		if (val)
+			*val = le16_to_cpu(v);
+		return true;
+	}
+	return false;
+}
+
+bool pull_s8(const char **p, size_t *max_len, int8_t *val)
+{
+	return pull_bytes(p, max_len, val, sizeof(*val));
+}
+
+bool pull_char(const char **p, size_t *max_len, char *val)
+{
+	return pull_bytes(p, max_len, val, sizeof(*val));
+}

+ 140 - 0
ccan/pushpull/pull.h

@@ -0,0 +1,140 @@
+/* CC0 license (public domain) - see LICENSE file for details */
+#ifndef CCAN_PUSHPULL_PULL_H
+#define CCAN_PUSHPULL_PULL_H
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/**
+ * pull_bytes - unmarshall bytes from push_bytes.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @dst: destination to copy bytes (or NULL to discard)
+ * @len: length to copy into @dst.
+ *
+ * If @max_len isn't long enough, @p is set to NULL, @max_len is set to
+ * 0 (making chaining safe), and false is returned.  Otherwise, @len
+ * bytes are copied from *@p into @dst, *@p is incremented by @len,
+ * @max_len is decremented by @len, and true is returned.
+ */
+bool pull_bytes(const char **p, size_t *max_len, void *dst, size_t len);
+
+/**
+ * pull_u64 - unmarshall a little-endian 64-bit value.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @val: the value (or NULL to discard)
+ *
+ * This pulls 8 bytes and converts from little-endian (if necessary for
+ * this platform).  It returns false and sets @p to NULL on error (ie. not
+ * enough bytes).
+ *
+ * Example:
+ * struct foo {
+ *	uint64_t vu64;
+ *	uint32_t vu32;
+ *	uint16_t vu16;
+ *	uint8_t vu8;
+ * };
+ *
+ * static bool pull_foo(const char **p, size_t *len, struct foo *foo)
+ * {
+ * 	pull_u64(p, len, &foo->vu64);
+ * 	pull_u32(p, len, &foo->vu32);
+ * 	pull_u16(p, len, &foo->vu16);
+ * 	pull_u8(p, len, &foo->vu8);
+ *
+ *      // p is set to NULL on error (we could also use return codes)
+ *	return p != NULL;
+ * }
+ */
+bool pull_u64(const char **p, size_t *max_len, uint64_t *val);
+
+/**
+ * pull_u32 - unmarshall a little-endian 32-bit value.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @val: the value (or NULL to discard)
+ *
+ * This pulls 4 bytes and converts from little-endian (if necessary for
+ * this platform).
+ */
+bool pull_u32(const char **p, size_t *max_len, uint32_t *val);
+
+/**
+ * pull_u16 - unmarshall a little-endian 16-bit value.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @val: the value (or NULL to discard)
+ *
+ * This pulls 2 bytes and converts from little-endian (if necessary for
+ * this platform).
+ */
+bool pull_u16(const char **p, size_t *max_len, uint16_t *val);
+
+/**
+ * pull_u8 - unmarshall a single byte value.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @val: the value (or NULL to discard)
+ *
+ * This pulls one byte.
+ */
+bool pull_u8(const char **p, size_t *max_len, uint8_t *val);
+#define pull_uchar pull_u8
+
+/**
+ * pull_s64 - unmarshall a little-endian 64-bit signed value.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @val: the value (or NULL to discard)
+ *
+ * This pulls 8 bytes and converts from little-endian (if necessary for
+ * this platform).
+ */
+bool pull_s64(const char **p, size_t *max_len, int64_t *val);
+
+/**
+ * pull_s32 - unmarshall a little-endian 32-bit signed value.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @val: the value (or NULL to discard)
+ *
+ * This pulls 4 bytes and converts from little-endian (if necessary for
+ * this platform).
+ */
+bool pull_s32(const char **p, size_t *max_len, int32_t *val);
+
+/**
+ * pull_s16 - unmarshall a little-endian 16-bit signed value.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @val: the value (or NULL to discard)
+ *
+ * This pulls 2 bytes and converts from little-endian (if necessary for
+ * this platform).
+ */
+bool pull_s16(const char **p, size_t *max_len, int16_t *val);
+
+/**
+ * pull_s8 - unmarshall a single byte signed value.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @val: the value (or NULL to discard)
+ *
+ * This pulls one byte.
+ */
+bool pull_s8(const char **p, size_t *max_len, int8_t *val);
+
+/**
+ * pull_char - unmarshall a single char value.
+ * @p: pointer to bytes to unmarshal
+ * @max_len: pointer to number of bytes in unmarshal buffer
+ * @val: the value (or NULL to discard)
+ *
+ * This pulls one character.
+ */
+bool pull_char(const char **p, size_t *max_len, char *val);
+#endif /* CCAN_PUSHPULL_PULL_H */

+ 73 - 0
ccan/pushpull/push.c

@@ -0,0 +1,73 @@
+/* CC0 license (public domain) - see LICENSE file for details */
+#include "push.h"
+#include <ccan/endian/endian.h>
+#include <string.h>
+
+static void *(*push_reallocfn)(void *ptr, size_t size) = realloc;
+
+bool push_bytes(char **p, size_t *len, const void *src, size_t srclen)
+{
+	char *n = push_reallocfn(*p, *len + srclen);
+	if (!n)
+		return false;
+	*p = n;
+	if (src)
+		memcpy(*p + *len, src, srclen);
+	else
+		memset(*p + *len, 0, srclen);
+	*len += srclen;
+	return true;
+}
+
+bool push_u64(char **p, size_t *len, uint64_t val)
+{
+	leint64_t v = cpu_to_le64(val);
+	return push_bytes(p, len, &v, sizeof(v));
+}
+
+bool push_u32(char **p, size_t *len, uint32_t val)
+{
+	leint32_t v = cpu_to_le32(val);
+	return push_bytes(p, len, &v, sizeof(v));
+}
+
+bool push_u16(char **p, size_t *len, uint16_t val)
+{
+	leint16_t v = cpu_to_le16(val);
+	return push_bytes(p, len, &v, sizeof(v));
+}
+
+bool push_u8(char **p, size_t *len, uint8_t val)
+{
+	return push_bytes(p, len, &val, sizeof(val));
+}
+
+bool push_s64(char **p, size_t *len, int64_t val)
+{
+	return push_u64(p, len, val);
+}
+
+bool push_s32(char **p, size_t *len, int32_t val)
+{
+	return push_u32(p, len, val);
+}
+
+bool push_s16(char **p, size_t *len, int16_t val)
+{
+	return push_u16(p, len, val);
+}
+
+bool push_s8(char **p, size_t *len, int8_t val)
+{
+	return push_u8(p, len, val);
+}
+
+bool push_char(char **p, size_t *len, char val)
+{
+	return push_u8(p, len, val);
+}
+
+void push_set_realloc(void *(*reallocfn)(void *ptr, size_t size))
+{
+	push_reallocfn = reallocfn;
+}

+ 121 - 0
ccan/pushpull/push.h

@@ -0,0 +1,121 @@
+/* CC0 license (public domain) - see LICENSE file for details */
+#ifndef CCAN_PUSHPULL_PUSH_H
+#define CCAN_PUSHPULL_PUSH_H
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/**
+ * push_bytes - marshall bytes into buffer
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @src: source to copy bytes from (or NULL to copy zeroes)
+ * @srclen: length to copy from @src.
+ *
+ * If realloc() fails, false is returned.  Otherwise, *@p is increased
+ * by @srclen bytes, *@len is incremented by @srclen, and bytes appended
+ * to *@p (from @src if non-NULL).
+ */
+bool push_bytes(char **p, size_t *len, const void *src, size_t srclen);
+
+/**
+ * push_u64 - marshall a 64-bit value into buffer (as little-endian)
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @val: the value to marshall.
+ *
+ * If realloc() fails, false is returned.
+ */
+bool push_u64(char **p, size_t *len, uint64_t val);
+
+/**
+ * push_u32 - marshall a 32-bit value into buffer (as little-endian)
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @val: the value to marshall.
+ *
+ * If realloc() fails, false is returned.
+ */
+bool push_u32(char **p, size_t *len, uint32_t val);
+
+/**
+ * push_u16 - marshall a 16-bit value into buffer (as little-endian)
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @val: the value to marshall.
+ *
+ * If realloc() fails, false is returned.
+ */
+bool push_u16(char **p, size_t *len, uint16_t val);
+
+/**
+ * push_u8 - marshall an 8-bit value into buffer
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @val: the value to marshall.
+ *
+ * If realloc() fails, false is returned.
+ */
+bool push_u8(char **p, size_t *len, uint8_t val);
+#define push_uchar push_u8
+
+/**
+ * push_u64 - marshall a signed 64-bit value into buffer (as little-endian)
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @val: the value to marshall.
+ *
+ * If realloc() fails, false is returned.
+ */
+bool push_s64(char **p, size_t *len, int64_t val);
+
+/**
+ * push_u32 - marshall a signed 32-bit value into buffer (as little-endian)
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @val: the value to marshall.
+ *
+ * If realloc() fails, false is returned.
+ */
+bool push_s32(char **p, size_t *len, int32_t val);
+
+/**
+ * push_u16 - marshall a signed 16-bit value into buffer (as little-endian)
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @val: the value to marshall.
+ *
+ * If realloc() fails, false is returned.
+ */
+bool push_s16(char **p, size_t *len, int16_t val);
+
+/**
+ * push_u8 - marshall a signed 8-bit value into buffer
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @val: the value to marshall.
+ *
+ * If realloc() fails, false is returned.
+ */
+bool push_s8(char **p, size_t *len, int8_t val);
+
+/**
+ * push_char - marshall a character into buffer
+ * @p: buffer of marshalled bytes
+ * @len: current length of @p buffer
+ * @val: the value to marshall.
+ *
+ * If realloc() fails, false is returned.
+ */
+bool push_char(char **p, size_t *len, char val);
+
+/**
+ * push_set_realloc - set function to use (instead of realloc).
+ * @reallocfn: new reallocation function.
+ *
+ * This can be used, for example, to cache reallocations.
+ */
+void push_set_realloc(void *(reallocfn)(void *ptr, size_t size));
+#endif /* CCAN_PUSHPULL_PUSH_H */

+ 7 - 0
ccan/pushpull/pushpull.h

@@ -0,0 +1,7 @@
+/* CC0 license (public domain) - see LICENSE file for details */
+#ifndef CCAN_PUSHPULL_H
+#define CCAN_PUSHPULL_H
+/* You can also include these independently, if you don't need both. */
+#include <ccan/pushpull/push.h>
+#include <ccan/pushpull/pull.h>
+#endif /* CCAN_PUSHPULL_H */

+ 150 - 0
ccan/pushpull/test/run.c

@@ -0,0 +1,150 @@
+#include <ccan/pushpull/pushpull.h>
+/* Include the C files directly. */
+#include <ccan/pushpull/push.c>
+#include <ccan/pushpull/pull.c>
+#include <ccan/tap/tap.h>
+
+struct foo {
+	uint64_t vu64;
+	uint32_t vu32;
+	uint16_t vu16;
+	uint8_t vu8;
+	unsigned char vuchar;
+	int64_t vs64;
+	int32_t vs32;
+	int16_t vs16;
+	int8_t vs8;
+	char vchar;
+	char bytes[100];
+};
+
+static void *fail_reallocfn(void *ptr, size_t size)
+{
+	return NULL;
+}
+
+static bool push_foo(char **p, size_t *len, const struct foo *foo)
+{
+	return push_u64(p, len, foo->vu64) &&
+		push_u32(p, len, foo->vu32) &&
+		push_u16(p, len, foo->vu16) &&
+		push_u8(p, len, foo->vu8) &&
+		push_uchar(p, len, foo->vuchar) &&
+		push_s64(p, len, foo->vs64) &&
+		push_s32(p, len, foo->vs32) &&
+		push_s16(p, len, foo->vs16) &&
+		push_s8(p, len, foo->vs8) &&
+		push_char(p, len, foo->vchar) &&
+		push_bytes(p, len, foo->bytes, sizeof(foo->bytes));
+}
+
+static bool pull_foo(const char **p, size_t *len, struct foo *foo)
+{
+	int ret;
+
+	ret = pull_u64(p, len, &foo->vu64) +
+		pull_u32(p, len, &foo->vu32) +
+		pull_u16(p, len, &foo->vu16) +
+		pull_u8(p, len, &foo->vu8) +
+		pull_uchar(p, len, &foo->vuchar) +
+		pull_s64(p, len, &foo->vs64) +
+		pull_s32(p, len, &foo->vs32) +
+		pull_s16(p, len, &foo->vs16) +
+		pull_s8(p, len, &foo->vs8) +
+		pull_char(p, len, &foo->vchar) +
+		pull_bytes(p, len, foo->bytes, sizeof(foo->bytes));
+
+	if (ret != 11)
+		ok1(len == 0 && *p == NULL);
+	return ret == 11;
+}
+
+static bool foo_equal(const struct foo *f1, const struct foo *f2)
+{
+	return f1->vu64 == f2->vu64 &&
+		f1->vu32 == f2->vu32 &&
+		f1->vu16 == f2->vu16 &&
+		f1->vu8 == f2->vu8 &&
+		f1->vuchar == f2->vuchar &&
+		f1->vs64 == f2->vs64 &&
+		f1->vs32 == f2->vs32 &&
+		f1->vs16 == f2->vs16 &&
+		f1->vs8 == f2->vs8 &&
+		f1->vchar == f2->vchar &&
+		memcmp(f1->bytes, f2->bytes, sizeof(f1->bytes)) == 0;
+}
+
+int main(void)
+{
+	char *buffer;
+	const char *p;
+	size_t len, left;
+	struct foo *foo, *foo2;
+
+	/* This is how many tests you plan to run */
+	plan_tests(17);
+
+	/* Valgrind will make sure we don't read padding! */
+	foo = malloc(sizeof(*foo));
+	foo->vu64 = 0x01020304050607ULL;
+	foo->vu32 = 0x08090a0b;
+	foo->vu16 = 0x0c0d;
+	foo->vu8 = 0x0e;
+	foo->vuchar = 0x0f;
+	foo->vs64 = -0x1011121314151617LL;
+	foo->vs32 = -0x18191a1b;
+	foo->vs16 = -0x1c1d;
+	foo->vs8 = -0x1e;
+	foo->vchar = -0x1f;
+	memset(foo->bytes, 0x20, sizeof(foo->bytes));
+	strcpy(foo->bytes, "This is a test");
+
+	buffer = malloc(1);
+	len = 0;
+	ok1(push_foo(&buffer, &len, foo));
+	ok1(len <= sizeof(*foo));
+
+	/* Triggers valgrind's uninitialized value warning */
+	ok1(!memchr(buffer, 0x21, len));
+
+	p = buffer;
+	left = len;
+	foo2 = malloc(sizeof(*foo2));
+	ok1(pull_foo(&p, &left, foo2));
+	ok1(left == 0);
+	ok1(p == buffer + len);
+	ok1(foo_equal(foo, foo2));
+
+	/* Too-small for pull, it should fail and set ptr/len to 0 */
+	p = buffer;
+	left = 0;
+	ok1(!pull_u64(&p, &left, &foo2->vu64));
+	ok1(p == NULL && left == 0);
+	/* Shouldn't change field! */
+	ok1(foo_equal(foo, foo2));
+
+	left = 7;
+	ok1(!pull_u64(&p, &left, &foo2->vu64));
+	ok1(p == NULL && left == 0);
+	/* Shouldn't change field! */
+	ok1(foo_equal(foo, foo2));
+
+	/* Discard should work. */
+	left = len;
+	ok1(pull_bytes(&p, &left, NULL, sizeof(foo->bytes)));
+	ok1(left == len - sizeof(foo->bytes));
+
+	/* Push failures should be clean. */
+	push_set_realloc(fail_reallocfn);
+	p = buffer;
+	left = len;
+	ok1(!push_u64(&buffer, &left, foo->vu64));
+	ok1(p == buffer && left == len);
+
+	free(buffer);
+	free(foo);
+	free(foo2);
+
+	/* This exits depending on whether all tests passed */
+	return exit_status();
+}