diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | common.c | 33 | ||||
-rw-r--r-- | common.h | 8 | ||||
-rw-r--r-- | pack.h | 16 | ||||
-rw-r--r-- | unpack.c | 123 | ||||
-rw-r--r-- | unpack_test.c | 110 |
7 files changed, 293 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64c75c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +*_test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5d2ed5d --- /dev/null +++ b/Makefile @@ -0,0 +1 @@ +unpack_test: unpack_test.o unpack.o common.o diff --git a/common.c b/common.c new file mode 100644 index 0000000..106fc7a --- /dev/null +++ b/common.c @@ -0,0 +1,33 @@ +#include <stddef.h> + +#include "common.h" +#include "pack.h" + +size_t getsize(char c) +{ + switch (c) { + case 'b': case 'B': + case 'x': + return 1; + case 'h': case 'H': + case 'i': case 'I': + return 2; + case 'l': case 'L': + case 'f': + return 4; + case 'q': case 'Q': + case 'd': + return 8; + case 's': default: return (size_t)-1; + } +} + +const char *pack_strerror(enum pack_status status) +{ + switch (status) { + case PACK_OK: return "Success"; + case PACK_FMTINVAL: return "Invalid format parameter"; + case PACK_TOOSMALL: return "Buffer too small"; + default: return "Invalid Status"; + } +} diff --git a/common.h b/common.h new file mode 100644 index 0000000..f5a39c9 --- /dev/null +++ b/common.h @@ -0,0 +1,8 @@ +#ifndef PACK_INTERNAL_H +#define PACK_INTERNAL_H + +#include <stddef.h> + +size_t getsize(char c); + +#endif // !PACK_INTERNAL_H @@ -0,0 +1,16 @@ +#ifndef PACK_H +#define PACK_H + +#include <stddef.h> + +enum pack_status { + PACK_OK, + PACK_FMTINVAL, + PACK_TOOSMALL, +}; + +enum pack_status unpack(void *buf, size_t size, const char *fmt, ...); + +const char *pack_strerror(enum pack_status status); + +#endif // !PACK_H diff --git a/unpack.c b/unpack.c new file mode 100644 index 0000000..2617e9a --- /dev/null +++ b/unpack.c @@ -0,0 +1,123 @@ +#include <ctype.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <limits.h> + +#include <stdio.h> + +#include "common.h" +#include "pack.h" + +typedef enum pack_status unpacker(void *buf, size_t size, va_list ap); + +enum endian { BIG, LITTLE }; + +uintmax_t read_val(unsigned char *buf, size_t size, enum endian e) +{ + unsigned long long val = 0; + + for (size_t i = 0; i < size; i++) + val |= (buf[i] & 0xff) << (e == LITTLE ? i : size - i - 1) * 8; + + return val; +} +#define X(M) \ + M(b, signed char ) \ + M(B, unsigned char ) \ + M(h, short ) \ + M(H, unsigned short ) \ + M(i, int ) \ + M(I, unsigned ) \ + M(l, long ) \ + M(L, unsigned long ) \ + M(q, long long) \ + M(Q, unsigned long long) + +enum pack_status unpack(void *buf_, size_t size, const char *fmt, ...) +{ + enum endian endianness = BIG; + unsigned char *buf = buf_; + size_t offset = 0; + va_list ap; + + va_start(ap, fmt); + + for (int i = 0; fmt[i] != '\0'; i++) { + bool sign = islower(fmt[i]); + size_t s; + union { + signed char *b; + unsigned char *B; + short *h; + unsigned short *H; + int *i; + unsigned int *I; + long *l; + unsigned long *L; + long long *q; + unsigned long long *Q; + } arg; + /*void *arg;*/ + union { uintmax_t u; intmax_t s; } val; + switch (fmt[i]) { + case '>': endianness = BIG; continue; + case '<': endianness = LITTLE; continue; + case 'b': arg.b = va_arg(ap, signed char *); + case 'B': arg.B = va_arg(ap, unsigned char *); + case 'h': arg.h = va_arg(ap, short *); + case 'H': arg.H = va_arg(ap, unsigned short *); + case 'i': arg.i = va_arg(ap, int *); + case 'I': arg.I = va_arg(ap, unsigned *); + case 'l': arg.l = va_arg(ap, long *); + case 'L': arg.L = va_arg(ap, unsigned long *); + case 'q': arg.q = va_arg(ap, long long *); + case 'Q': arg.Q = va_arg(ap, unsigned long long *); + case 'x': break; + return PACK_FMTINVAL; + } + + s = getsize(fmt[i]); + if (s == (size_t)-1) return PACK_FMTINVAL; + + if (size - offset < s) return PACK_TOOSMALL; + + if (fmt[i] == 'x') goto skip; + + val.u = read_val(buf, s, endianness); + + fprintf(stderr, "%zu, %llu\n", s, val.u); + + if (sign) { + intmax_t vals; + if (!(val.u & (1llu << (s * 8 - 1)))) { + vals = val.u; + } else { + uintmax_t offt = UINTMAX_MAX >> (sizeof offt * CHAR_BIT - s * 8); + vals = val.u - offt - 1; + } + val.s = vals; + fprintf(stderr, "signed %lld\n", val.s); + } + + switch (fmt[i]) { +#define D(t, f) fprintf(stderr, "(%p) "#t " = %" #f "\n", (void *)arg.t, *arg.t) + case 'b': *arg.b = val.s; D(b, d); break; + case 'B': *arg.B = val.u; D(B, u); break; + case 'h': *arg.h = val.s; D(h, d); break; + case 'H': *arg.H = val.u; D(H, u); break; + case 'i': *arg.i = val.s; D(i, d); break; + case 'I': *arg.I = val.u; D(I, u); break; + case 'l': *arg.l = val.s; D(l, ld); break; + case 'L': *arg.L = val.u; D(L, lu); break; + case 'q': *arg.q = val.s; D(q, lld); break; + case 'Q': *arg.Q = val.u; D(Q, llu); break; + } +skip: + offset += s; + } + + va_end(ap); + + return PACK_OK; +} diff --git a/unpack_test.c b/unpack_test.c new file mode 100644 index 0000000..8eee6b3 --- /dev/null +++ b/unpack_test.c @@ -0,0 +1,110 @@ +#include <stdio.h> +#include <stdbool.h> + +#include "pack.h" + +typedef bool test_func(void); + +struct test { + test_func *func; + char *desc; +}; + +#define TEST(name) bool test_##name(void) +#define TEST_ENTRY(name, description) { test_##name, description } +#define TEST_ENTRY_END { NULL, NULL } +#define DATA(...) (unsigned char []){ __VA_ARGS__ }, sizeof (unsigned char []){ __VA_ARGS__ } +#define CHECK(test) if (!(test)) { puts("! " #test); return false; } + +#define CHECK_UNPACK(data, fmt, ...) do { \ + enum pack_status s = unpack(data, fmt, __VA_ARGS__); \ + if (s != PACK_OK) { \ + printf(__FILE__ ":%d unpack(" #data ", " #fmt ", ...) -> %s (%d)\n", __LINE__, pack_strerror(s), s); \ + return false; \ + } \ +} while (0) +#define CHECK_EQUAL(a, b) if (a != b) { printf(__FILE__ ":%d %d != %d\n", __LINE__, a, b); return false; } + +TEST(schar) +{ + signed char c = __LINE__; + fprintf(stderr, "Address of c: %p\n", (void *)&c); + CHECK_UNPACK(DATA(0), "b", &c); + CHECK_EQUAL(c, 0); + CHECK_UNPACK(DATA(1), "b", &c); + CHECK_EQUAL(c, 1); + CHECK_UNPACK(DATA(127), "b", &c); + CHECK_EQUAL(c, 127); + CHECK_UNPACK(DATA(255), "b", &c); + CHECK_EQUAL(c, -1); + CHECK_UNPACK(DATA(128), "b", &c); + CHECK_EQUAL(c, -128); + return true; +} + +TEST(uchar) +{ + unsigned char c = __LINE__; + fprintf(stderr, "Address of c: %p\n", (void *)&c); + CHECK_UNPACK(DATA(0), "B", &c); + CHECK_EQUAL(c, 0); + CHECK_UNPACK(DATA(1), "B", &c); + CHECK_EQUAL(c, 1); + CHECK_UNPACK(DATA(255), "B", &c); + CHECK_EQUAL(c, 255); + return true; +} + +TEST(sshort) +{ + short s = __LINE__; + fprintf(stderr, "Address of s: %p\n", (void *)&s); + CHECK_UNPACK(DATA(0, 0), "h", &s); + CHECK_EQUAL(s, 0); + CHECK_UNPACK(DATA(0, 0), ">h", &s); + CHECK_EQUAL(s, 0); + CHECK_UNPACK(DATA(0, 0), "<h", &s); + CHECK_EQUAL(s, 0); + CHECK_UNPACK(DATA(0, 1), "h", &s); + CHECK_EQUAL(s, 1); + CHECK_UNPACK(DATA(0, 1), ">h", &s); + CHECK_EQUAL(s, 1); + CHECK_UNPACK(DATA(1, 0), "<h", &s); + CHECK_EQUAL(s, 1); + CHECK_UNPACK(DATA(0x7f, 0xff), "h", &s); + CHECK_EQUAL(s, 32767); + CHECK_UNPACK(DATA(0x7f, 0xff), ">h", &s); + CHECK_EQUAL(s, 32767); + CHECK_UNPACK(DATA(0xff, 0x7f), "<h", &s); + CHECK_EQUAL(s, 32767); + CHECK_UNPACK(DATA(0xff, 0xff), "h", &s); + CHECK_EQUAL(s, -1); + CHECK_UNPACK(DATA(0xff, 0xff), ">h", &s); + CHECK_EQUAL(s, -1); + CHECK_UNPACK(DATA(0xff, 0xff), "<h", &s); + CHECK_EQUAL(s, -1); + CHECK_UNPACK(DATA(0x80, 0x00), "h", &s); + CHECK_EQUAL(s, -32768); + CHECK_UNPACK(DATA(0x80, 0x00), ">h", &s); + CHECK_EQUAL(s, -32768); + CHECK_UNPACK(DATA(0x00, 0x80), "<h", &s); + CHECK_EQUAL(s, -32768); +} + +TEST(ushort) +{ + unsigned short s; +} + +int main(void) +{ + struct test tests[] = { + TEST_ENTRY(schar, "schar unpacking"), + TEST_ENTRY(uchar, "uchar unpacking"), + TEST_ENTRY(sshort, "sshort unpacking"), + TEST_ENTRY_END + }; + + for (int i = 0; tests[i].func != NULL; i++) + printf("%s %s\n", tests[i].func() ? " OK " : "FAIL", tests[i].desc); +} |