summaryrefslogtreecommitdiffstats
path: root/pb_decode.c
diff options
context:
space:
mode:
Diffstat (limited to 'pb_decode.c')
-rw-r--r--pb_decode.c274
1 files changed, 274 insertions, 0 deletions
diff --git a/pb_decode.c b/pb_decode.c
new file mode 100644
index 00000000..884cf18e
--- /dev/null
+++ b/pb_decode.c
@@ -0,0 +1,274 @@
+/* pb_decode.c -- decode a protobuf using callback functions
+ *
+ * 2011 Petteri Aimonen <jpa@kapsi.fi>
+ */
+
+#include "pb_decode.h"
+
+const pb_decoder_t PB_DECODERS[PB_LAST_ACT] = {
+ NULL,
+ &pb_dec_uint32,
+ &pb_dec_sint32,
+ &pb_dec_uint32, // Cast to int32
+ &pb_dec_fixed32,
+ &pb_dec_fixed32, // Cast to int32
+ &pb_dec_uint64,
+ &pb_dec_sint64,
+ &pb_dec_uint64, // Cast to int64
+ &pb_dec_fixed64,
+ &pb_dec_fixed64, // Cast to int64
+ &pb_dec_bool,
+ &pb_dec_float,
+ &pb_dec_double,
+ &pb_dec_bytes,
+ &pb_dec_string,
+ &pb_dec_submessage
+};
+
+enum wire_type {
+ WT_VARINT = 0,
+ WT_64BIT = 1,
+ WT_STRING = 2,
+ WT_32BIT = 5
+};
+
+// Note: pb_decode_varint32 is a bit un-orthodox:
+// it will refuse to decode values that exceed uint32 range.
+// The Google implementation would simply cast to 32 bits.
+bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest)
+{
+ char byte;
+ int bitpos = 0;
+ *dest = 0;
+
+ while (bitpos < 32 && pb_read(stream, &byte, 1))
+ {
+ *dest |= (byte & 0x7F) << bitpos;
+ bitpos += 7;
+
+ if (!(byte & 0x80))
+ return true;
+ }
+
+ return false;
+}
+
+bool pb_decode_varint64(pb_istream_t *stream, uint64_t *dest)
+{
+ char byte;
+ int bitpos = 0;
+ *dest = 0;
+
+ while (bitpos < 64 && pb_read(stream, &byte, 1))
+ {
+ *dest |= (byte & 0x7F) << bitpos;
+ bitpos += 7;
+
+ if (!(byte & 0x80))
+ return true;
+ }
+
+ return false;
+}
+
+bool pb_skip_varint(pb_istream_t *stream)
+{
+ char byte;
+ do
+ {
+ if (!pb_read(stream, &byte, 1))
+ return false;
+ } while (byte & 0x80);
+ return true;
+}
+
+bool pb_skip_string(pb_istream_t *stream)
+{
+ uint32_t length;
+ if (!pb_decode_varint32(stream, &length))
+ return false;
+
+ return pb_read(stream, NULL, length);
+}
+
+bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest)
+{
+ while (stream->bytes_left)
+ {
+ uint32_t temp;
+ if (!pb_decode_varint32(stream, &temp))
+ return false;
+
+ int field_number = temp >> 3;
+ int wire_type = temp & 7;
+
+ const pb_field_t *field = fields;
+ while (field->field_number != 0)
+ {
+ if (field->field_number != field_number)
+ {
+ field++;
+ continue;
+ }
+
+ void *destfield = dest + field->offset;
+
+ if (field->action == PB_ACT_HAS)
+ {
+ *(bool*)destfield = true;
+ field++;
+ continue;
+ }
+
+ pb_decoder_t func = PB_DECODERS[field->action];
+ if (!func(stream, field, destfield))
+ return false;
+
+ break;
+ }
+
+ if (field->field_number == 0) // No match found, skip data
+ {
+ bool status = false;
+ switch (wire_type)
+ {
+ case WT_VARINT:
+ status = pb_skip_varint(stream);
+ break;
+
+ case WT_64BIT:
+ status = pb_read(stream, NULL, 8);
+ break;
+
+ case WT_STRING:
+ status = pb_skip_string(stream);
+ break;
+
+ case WT_32BIT:
+ status = pb_read(stream, NULL, 4);
+ break;
+ }
+
+ if (!status)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool pb_dec_uint32(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ return pb_decode_varint32(stream, (uint32_t*)dest);
+}
+
+bool pb_dec_sint32(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ uint32_t *x = (uint32_t*)dest;
+ bool status = pb_decode_varint32(stream, x);
+ *x = (*x >> 1) ^ -(int32_t)(*x & 1);
+ return status;
+}
+
+bool pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ char bytes[4] = {0};
+ bool status = pb_read(stream, bytes, 4);
+ *(uint32_t*)dest =
+ bytes[0] | ((uint32_t)bytes[1] << 8) |
+ ((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24);
+ return status;
+}
+
+bool pb_dec_uint64(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ return pb_decode_varint64(stream, (uint64_t*)dest);
+}
+
+bool pb_dec_sint64(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ uint64_t *x = (uint64_t*)dest;
+ bool status = pb_decode_varint64(stream, x);
+ *x = (*x >> 1) ^ -(int64_t)(*x & 1);
+ return status;
+}
+
+bool pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ char bytes[8] = {0};
+ bool status = pb_read(stream, bytes, 8);
+ *(uint64_t*)dest =
+ (uint64_t)bytes[0] | ((uint64_t)bytes[1] << 8) |
+ ((uint64_t)bytes[2] << 16) | ((uint64_t)bytes[3] << 24) |
+ ((uint64_t)bytes[4] << 32) | ((uint64_t)bytes[5] << 40) |
+ ((uint64_t)bytes[6] << 48) | ((uint64_t)bytes[7] << 56);
+ return status;
+}
+
+bool pb_dec_bool(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ uint32_t temp = 0;
+ bool status = pb_decode_varint32(stream, &temp);
+ *(bool*)dest = !!temp;
+ return status;
+}
+
+bool pb_dec_float(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ return pb_read(stream, (char*)dest, sizeof(float));
+}
+
+bool pb_dec_double(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ return pb_read(stream, (char*)dest, sizeof(double));
+}
+
+bool pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ pb_bytearray_t *x = (pb_bytearray_t*)dest;
+ uint32_t temp;
+ if (!pb_decode_varint32(stream, &temp))
+ return false;
+ x->size = temp;
+
+ if (x->size > field->fieldsize)
+ return false;
+
+ return pb_read(stream, x->bytes, x->size);
+}
+
+bool pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ uint32_t size;
+ if (!pb_decode_varint32(stream, &size))
+ return false;
+
+ if (size > field->fieldsize - 1)
+ return false;
+
+ bool status = pb_read(stream, (char*)dest, size);
+ *((char*)dest + size) = 0;
+ return status;
+}
+
+bool pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ pb_callback_t *x = (pb_callback_t*)dest;
+
+ if (x->funcs.decode == NULL)
+ return pb_skip_string(stream);
+
+ uint32_t size;
+ if (!pb_decode_varint32(stream, &size))
+ return false;
+
+ if (stream->bytes_left < size)
+ return false;
+
+ // Make a limited-length istream for decoding submessage
+ pb_istream_t shortstream = *stream;
+ shortstream.bytes_left = size;
+ bool status = x->funcs.decode(&shortstream, field, x->arg);
+ stream->bytes_left -= size - shortstream.bytes_left;
+ return status;
+}