Skip to content

Instantly share code, notes, and snippets.

@nilput
Last active May 23, 2019 09:11
Show Gist options
  • Select an option

  • Save nilput/9c5e41649abbe3d9f323a0c23e50285e to your computer and use it in GitHub Desktop.

Select an option

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.
///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