Commit 17aa322a authored by Rusty Russell's avatar Rusty Russell

pushpull: new module.

Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 2ea90b45
......@@ -80,6 +80,7 @@ MODS_WITH_SRC := antithread \
ogg_to_pcm \
opt \
ptr_valid \
pushpull \
rbtree \
rbuf \
read_write_all \
......
../../licenses/CC0
\ No newline at end of file
#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;
}
/* 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));
}
/* 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 */
/* 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;
}
/* 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 */
/* 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 */
#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();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment