From 17aa322abd7b785330ab49e348144be940b3d1d8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 11 Nov 2014 15:53:27 +1030 Subject: [PATCH] pushpull: new module. Signed-off-by: Rusty Russell --- Makefile-ccan | 1 + ccan/pushpull/LICENSE | 1 + ccan/pushpull/_info | 76 ++++++++++++++++++++ ccan/pushpull/pull.c | 105 +++++++++++++++++++++++++++ ccan/pushpull/pull.h | 140 ++++++++++++++++++++++++++++++++++++ ccan/pushpull/push.c | 73 +++++++++++++++++++ ccan/pushpull/push.h | 121 +++++++++++++++++++++++++++++++ ccan/pushpull/pushpull.h | 7 ++ ccan/pushpull/test/run.c | 150 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 674 insertions(+) create mode 120000 ccan/pushpull/LICENSE create mode 100644 ccan/pushpull/_info create mode 100644 ccan/pushpull/pull.c create mode 100644 ccan/pushpull/pull.h create mode 100644 ccan/pushpull/push.c create mode 100644 ccan/pushpull/push.h create mode 100644 ccan/pushpull/pushpull.h create mode 100644 ccan/pushpull/test/run.c diff --git a/Makefile-ccan b/Makefile-ccan index 74916d2a..a2fa8f3a 100644 --- a/Makefile-ccan +++ b/Makefile-ccan @@ -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 index 00000000..b7951dab --- /dev/null +++ b/ccan/pushpull/LICENSE @@ -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 index 00000000..536744b0 --- /dev/null +++ b/ccan/pushpull/_info @@ -0,0 +1,76 @@ +#include "config.h" +#include +#include + +/** + * 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 + * License: CC0 (Public domain) + * + * Example: + * #include + * #include + * #include + * #include + * #include + * #include + * + * 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] [...]", 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 index 00000000..be26b41d --- /dev/null +++ b/ccan/pushpull/pull.c @@ -0,0 +1,105 @@ +/* CC0 license (public domain) - see LICENSE file for details */ +#include "pull.h" +#include +#include + +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 index 00000000..e675a8dd --- /dev/null +++ b/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 +#include +#include + +/** + * 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 index 00000000..c7c2250f --- /dev/null +++ b/ccan/pushpull/push.c @@ -0,0 +1,73 @@ +/* CC0 license (public domain) - see LICENSE file for details */ +#include "push.h" +#include +#include + +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 index 00000000..55274b97 --- /dev/null +++ b/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 +#include +#include + +/** + * 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 index 00000000..e01bc09e --- /dev/null +++ b/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 +#include +#endif /* CCAN_PUSHPULL_H */ diff --git a/ccan/pushpull/test/run.c b/ccan/pushpull/test/run.c new file mode 100644 index 00000000..f51ef202 --- /dev/null +++ b/ccan/pushpull/test/run.c @@ -0,0 +1,150 @@ +#include +/* Include the C files directly. */ +#include +#include +#include + +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(); +} -- 2.39.2