Last active
May 23, 2019 09:11
-
-
Save nilput/9c5e41649abbe3d9f323a0c23e50285e to your computer and use it in GitHub Desktop.
an example showing serialization an deserialization routines for structs, ending up with a representation that can be used for network or files in a cross platform way.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ///author: github.com/nilput <[email protected]> | |
| //license: BSD-3 | |
| #include <assert.h> | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <stdio.h> | |
| #include <limits.h> | |
| #if CHAR_BIT != 8 | |
| #error "works only on 8bit byte platforms" | |
| #endif | |
| //a small library that implements a bytestream and a couple of functions for reading and writing some C types | |
| enum bytestream_error { | |
| BYTESTREAM_OK = 0, | |
| BYTESTREAM_NMEM, | |
| BYTESTREAM_OUT_OF_RANGE, | |
| }; | |
| struct bytestream { | |
| uint8_t *bytes; | |
| size_t length; | |
| size_t cap; | |
| size_t cursor; //used when reading | |
| }; | |
| int bytestream_ensure_write(struct bytestream *bs, size_t additional_size) { | |
| if (bs->length + additional_size > bs->cap) { | |
| bs->bytes = realloc(bs->bytes, bs->length + additional_size); | |
| if (!bs->bytes) { | |
| return BYTESTREAM_NMEM; | |
| } | |
| } | |
| return 0; | |
| } | |
| int bytestream_init(struct bytestream *bs) { | |
| memset(bs, 0, sizeof *bs); | |
| return 0; | |
| } | |
| void bytestream_seekset(struct bytestream *bs, size_t offset) { | |
| bs->cursor = offset; | |
| } | |
| void bytestream_deinit(struct bytestream *bs) { | |
| free(bs->bytes); | |
| bs->bytes = NULL; | |
| } | |
| int bytestream_append_uint32(struct bytestream *bs, uint32_t in) { | |
| uint8_t bytes[4] = { | |
| (in >> 0) & 0xFF, | |
| (in >> 8) & 0xFF, | |
| (in >> 16) & 0xFF, | |
| (in >> 24) & 0xFF, | |
| }; | |
| int rv = bytestream_ensure_write(bs, 4); | |
| if (rv != 0) | |
| return rv; | |
| memcpy(bs->bytes + bs->length, bytes, 4); | |
| bs->length += 4; | |
| return 0; | |
| } | |
| int bytestream_get_uint32(struct bytestream *bs, uint32_t *out) { | |
| if (bs->cursor + 4 > bs->length) | |
| return 1; | |
| *out = (bs->bytes[bs->cursor+0] << 0) | | |
| (bs->bytes[bs->cursor+1] << 8) | | |
| (bs->bytes[bs->cursor+2] << 16) | | |
| (bs->bytes[bs->cursor+3] << 24); | |
| bs->cursor += 4; | |
| return 0; | |
| } | |
| #define MASK_31ST_BIT (1U << 31) | |
| #define MASK_31_BITS (0xFFFFFFFFU & ~(1U << 31)) | |
| //this can only store 31 bits worth of integerness, which is not the full range of int32 | |
| int bytestream_append_int32(struct bytestream *bs, int32_t in) { | |
| uint32_t sign_bit = 0; | |
| if (in < 0) { | |
| in = -in; | |
| sign_bit = MASK_31ST_BIT; | |
| } | |
| uint32_t rep = ((uint32_t)in & MASK_31_BITS) | sign_bit; | |
| return bytestream_append_uint32(bs, rep); | |
| } | |
| int bytestream_get_int32(struct bytestream *bs, int32_t *out) { | |
| uint32_t rep; | |
| int rv = bytestream_get_uint32(bs, &rep); | |
| if (rv != 0) | |
| return rv; | |
| int32_t value = rep & MASK_31_BITS; | |
| if (rep & MASK_31ST_BIT) | |
| value = -value; | |
| *out = value; | |
| return 0; | |
| } | |
| //this isn't ideal, it assumes both machines use the same float representation, but this assumption usually works | |
| //a string representation might actually be better than a binary represnentation for portability :) | |
| union flt { | |
| float f; | |
| uint32_t u; | |
| } u; | |
| int bytestream_append_float(struct bytestream *bs, float in) { | |
| assert(sizeof(union flt) <= sizeof(uint32_t)); | |
| union flt u; | |
| u.f = in; | |
| return bytestream_append_uint32(bs, u.u); | |
| } | |
| //the following functions return non 0 on failure | |
| int bytestream_get_float(struct bytestream *bs, float *out) { | |
| assert(sizeof(union flt) <= sizeof(uint32_t)); //not a portable assumption | |
| union flt u; | |
| int rv = bytestream_get_uint32(bs, &u.u); | |
| if (rv != 0) | |
| return rv; | |
| *out = u.f; | |
| return 0; | |
| } | |
| //example usage | |
| struct bar { | |
| float f; | |
| char c; | |
| int w; | |
| }; | |
| struct foo { | |
| unsigned a; | |
| struct bar b; | |
| }; | |
| int serialize_bar(struct bytestream *bs, const struct bar *in) { | |
| int rv = 0; | |
| rv = bytestream_append_float(bs, in->f); | |
| if (rv != 0) | |
| return rv; | |
| rv = bytestream_append_int32(bs, in->c); | |
| if (rv != 0) | |
| return rv; | |
| rv = bytestream_append_int32(bs, in->w); | |
| return rv; | |
| } | |
| int serialize_foo(struct bytestream *bs, const struct foo *in) { | |
| assert(sizeof(unsigned) <= sizeof(uint32_t)); | |
| int rv = 0; | |
| rv = bytestream_append_uint32(bs, in->a); | |
| if (rv != 0) | |
| return rv; | |
| rv = serialize_bar(bs, &in->b); | |
| return rv; | |
| } | |
| int deserialize_bar(struct bytestream *bs, struct bar *out) { | |
| int rv = 0; | |
| float f; | |
| int32_t i32; | |
| rv = bytestream_get_float(bs, &f); | |
| if (rv != 0) | |
| return rv; | |
| out->f = f; | |
| rv = bytestream_get_int32(bs, &i32); | |
| if (rv != 0) | |
| return rv; | |
| out->c = i32; | |
| rv = bytestream_get_int32(bs, &i32); | |
| if (rv != 0) | |
| return rv; | |
| out->w = i32; | |
| return 0; | |
| } | |
| int deserialize_foo(struct bytestream *bs, struct foo *out) { | |
| uint32_t u32; | |
| int rv = bytestream_get_uint32(bs, &u32); | |
| if (rv != 0) | |
| return rv; | |
| out->a = u32; | |
| rv = deserialize_bar(bs, &out->b); | |
| return rv; | |
| } | |
| void represent_struct_foo(struct foo f) { | |
| printf( | |
| "struct foo {\n" | |
| " unsigned a; = %u\n" | |
| " struct bar {\n" | |
| " float f; = %f\n" | |
| " char c; = %d\n" | |
| " int w; = %d\n" | |
| " } b;\n" | |
| "};\n", f.a, f.b.f, (int)f.b.c, f.b.w); | |
| } | |
| void die(const char *msg) { | |
| fprintf(stderr, "error: %s\n", msg); | |
| } | |
| int main(void) { | |
| struct foo example_in = {1, {2.5f, 3, -4}}; | |
| struct foo example_out; | |
| struct bytestream bs; | |
| int rv = bytestream_init(&bs); | |
| if (rv != 0) | |
| die("couldn't initialize bytestream"); | |
| represent_struct_foo(example_in); | |
| rv = serialize_foo(&bs, &example_in); | |
| if (rv != 0) | |
| die("couldn't serialize struct foo"); | |
| printf("serialized struct foo, size of representation: %zu bytes.\n", bs.length); | |
| //make cursor point to the first byte | |
| bytestream_seekset(&bs, 0); | |
| rv = deserialize_foo(&bs, &example_out); | |
| if (rv != 0) | |
| die("couldn't deserialize struct foo"); | |
| printf("deserialized struct foo\n"); | |
| represent_struct_foo(example_out); | |
| bytestream_deinit(&bs); | |
| return 0; | |
| } | |
| /* output: | |
| * | |
| * struct foo { | |
| * unsigned a; = 1 | |
| * struct bar { | |
| * float f; = 2.500000 | |
| * char c; = 3 | |
| * int w; = -4 | |
| * } b; | |
| * }; | |
| * serialized struct foo, size of representation: 16 bytes. | |
| * deserialized struct foo | |
| * struct foo { | |
| * unsigned a; = 1 | |
| * struct bar { | |
| * float f; = 2.500000 | |
| * char c; = 3 | |
| * int w; = -4 | |
| * } b; | |
| * }; | |
| * | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment