]> git.ozlabs.org Git - ccan/commitdiff
pushpull: new module.
authorRusty Russell <rusty@rustcorp.com.au>
Tue, 11 Nov 2014 05:23:27 +0000 (15:53 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Tue, 11 Nov 2014 05:28:20 +0000 (15:58 +1030)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Makefile-ccan
ccan/pushpull/LICENSE [new symlink]
ccan/pushpull/_info [new file with mode: 0644]
ccan/pushpull/pull.c [new file with mode: 0644]
ccan/pushpull/pull.h [new file with mode: 0644]
ccan/pushpull/push.c [new file with mode: 0644]
ccan/pushpull/push.h [new file with mode: 0644]
ccan/pushpull/pushpull.h [new file with mode: 0644]
ccan/pushpull/test/run.c [new file with mode: 0644]

index 74916d2a99859cb9278654a275b5d9e619738fb4..a2fa8f3a5da1a40978bad37f01a54e74fe727458 100644 (file)
@@ -80,6 +80,7 @@ MODS_WITH_SRC := antithread \
        ogg_to_pcm \
        opt \
        ptr_valid \
+       pushpull \
        rbtree \
        rbuf \
        read_write_all \
diff --git a/ccan/pushpull/LICENSE b/ccan/pushpull/LICENSE
new file mode 120000 (symlink)
index 0000000..b7951da
--- /dev/null
@@ -0,0 +1 @@
+../../licenses/CC0
\ No newline at end of file
diff --git a/ccan/pushpull/_info b/ccan/pushpull/_info
new file mode 100644 (file)
index 0000000..536744b
--- /dev/null
@@ -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;
+}
diff --git a/ccan/pushpull/pull.c b/ccan/pushpull/pull.c
new file mode 100644 (file)
index 0000000..be26b41
--- /dev/null
@@ -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));
+}
diff --git a/ccan/pushpull/pull.h b/ccan/pushpull/pull.h
new file mode 100644 (file)
index 0000000..e675a8d
--- /dev/null
@@ -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 */
diff --git a/ccan/pushpull/push.c b/ccan/pushpull/push.c
new file mode 100644 (file)
index 0000000..c7c2250
--- /dev/null
@@ -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;
+}
diff --git a/ccan/pushpull/push.h b/ccan/pushpull/push.h
new file mode 100644 (file)
index 0000000..55274b9
--- /dev/null
@@ -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 */
diff --git a/ccan/pushpull/pushpull.h b/ccan/pushpull/pushpull.h
new file mode 100644 (file)
index 0000000..e01bc09
--- /dev/null
@@ -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 */
diff --git a/ccan/pushpull/test/run.c b/ccan/pushpull/test/run.c
new file mode 100644 (file)
index 0000000..f51ef20
--- /dev/null
@@ -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();
+}