Merge branch 'fastbinary'
git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@674688 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/rb/ext/binaryprotocolaccelerated.c b/lib/rb/ext/binaryprotocolaccelerated.c
new file mode 100644
index 0000000..2725bfc
--- /dev/null
+++ b/lib/rb/ext/binaryprotocolaccelerated.c
@@ -0,0 +1,1231 @@
+// Half of this file comes from contributions from Nitay Joffe (nitay@powerset.com)
+// Much of the rest (almost) directly ported (or pulled) from thrift-py's fastbinary.c
+// Everything else via Kevin Clark (kevin@powerset.com)
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <ruby.h>
+#include <st.h>
+#include <netinet/in.h>
+
+// #define __DEBUG__
+
+#ifndef HAVE_STRLCPY
+
+static
+size_t
+strlcpy (char *dst, const char *src, size_t dst_sz)
+{
+ size_t n;
+
+ for (n = 0; n < dst_sz; n++) {
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+
+ if (n < dst_sz)
+ return n;
+ if (n > 0)
+ *(dst - 1) = '\0';
+ return n + strlen (src);
+}
+
+#endif
+
+#define dbg() fprintf(stderr, "%s:%d\n", __FUNCTION__, __LINE__)
+
+
+// TODO (kevinclark): This was here from the patch/python. Not sure
+// If it's actually that big a pain. Need to look into pulling
+// From the right place
+
+// Stolen out of TProtocol.h.
+// It would be a huge pain to have both get this from one place.
+
+enum TType {
+ T_STOP = 0,
+ T_BOOL = 2,
+ T_BYTE = 3,
+ T_I16 = 6,
+ T_I32 = 8,
+ T_I64 = 10,
+ T_DBL = 4,
+ T_STR = 11,
+ T_STRCT = 12,
+ T_MAP = 13,
+ T_SET = 14,
+ T_LIST = 15
+ // T_VOID = 1,
+ // T_I08 = 3,
+ // T_U64 = 9,
+ // T_UTF7 = 11,
+ // T_UTF8 = 16,
+ // T_UTF16 = 17
+};
+
+#define IS_CONTAINER(x) (x == T_MAP || x == T_SET || x == T_LIST)
+
+// Same comment as the enum. Sorry.
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#endif
+
+#ifndef __BYTE_ORDER
+# if defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)
+# define __BYTE_ORDER BYTE_ORDER
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __BIG_ENDIAN BIG_ENDIAN
+# else
+# error "Cannot determine endianness"
+# endif
+#endif
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+# define ntohll(n) (n)
+# define htonll(n) (n)
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+# if defined(__GNUC__) && defined(__GLIBC__)
+# include <byteswap.h>
+# define ntohll(n) bswap_64(n)
+# define htonll(n) bswap_64(n)
+# else /* GNUC & GLIBC */
+# define ntohll(n) ( (((unsigned long long)ntohl(n)) << 32) + ntohl(n >> 32) )
+# define htonll(n) ( (((unsigned long long)htonl(n)) << 32) + htonl(n >> 32) )
+# endif /* GNUC & GLIBC */
+#else /* __BYTE_ORDER */
+# error "Can't define htonll or ntohll!"
+#endif
+
+
+// -----------------------------------------------------------------------------
+// Cached interned strings and such
+// -----------------------------------------------------------------------------
+
+static VALUE class_tbpa;
+static VALUE m_thrift;
+static VALUE rb_cSet;
+static ID type_sym;
+static ID class_sym;
+static ID key_sym;
+static ID value_sym;
+static ID element_sym;
+static ID name_sym;
+static ID fields_id;
+static ID consume_bang_id;
+static ID string_buffer_id;
+static ID borrow_id;
+static ID keys_id;
+static ID entries_id;
+
+static const uint32_t VERSION_MASK = 0xffff0000;
+static const uint32_t VERSION_1 = 0x80010000;
+
+// -----------------------------------------------------------------------------
+// Structs so I don't have to keep calling rb_hash_aref
+// -----------------------------------------------------------------------------
+
+// { :type => field[:type],
+// :class => field[:class],
+// :key => field[:key],
+// :value => field[:value],
+// :element => field[:element] }
+
+struct _thrift_map;
+struct _field_spec;
+
+typedef union {
+ VALUE class;
+ struct _thrift_map* map;
+ struct _field_spec* element;
+} container_data;
+
+typedef struct _field_spec {
+ int type;
+ char* name;
+ container_data data;
+} field_spec;
+
+typedef struct _thrift_map {
+ field_spec* key;
+ field_spec* value;
+} thrift_map;
+
+
+static void free_field_spec(field_spec* spec) {
+ switch(spec->type) {
+ case T_LIST:
+ case T_SET:
+ free_field_spec(spec->data.element);
+ break;
+
+ case T_MAP:
+ free_field_spec(spec->data.map->key);
+ free_field_spec(spec->data.map->value);
+ free(spec->data.map);
+ break;
+ }
+
+ free(spec);
+}
+
+// Parses a ruby field spec into a C struct
+//
+// Simple fields look like:
+// { :name => .., :type => .. }
+// Structs add the :class attribute
+// Maps adds :key and :value attributes, field specs
+// Lists and Sets add an :element, a field spec
+static field_spec* parse_field_spec(VALUE field_data) {
+ int type = NUM2INT(rb_hash_aref(field_data, type_sym));
+ VALUE name = rb_hash_aref(field_data, name_sym);
+ field_spec* spec = (field_spec *) malloc(sizeof(field_spec));
+
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+ bzero(spec, sizeof(field_spec));
+#endif
+
+ spec->type = type;
+
+ if (Qnil != name) {
+ spec->name = StringValuePtr(name);
+ } else {
+ spec->name = NULL;
+ }
+
+ switch(type) {
+ case T_STRCT: {
+ spec->data.class = rb_hash_aref(field_data, class_sym);
+ break;
+ }
+
+ case T_MAP: {
+ VALUE key_fields = rb_hash_aref(field_data, key_sym);
+ VALUE value_fields = rb_hash_aref(field_data, value_sym);
+ thrift_map* map = (thrift_map *) malloc(sizeof(thrift_map));
+
+ map->key = parse_field_spec(key_fields);
+ map->value = parse_field_spec(value_fields);
+ spec->data.map = map;
+
+ break;
+ }
+
+ case T_LIST:
+ case T_SET:
+ {
+ VALUE list_fields = rb_hash_aref(field_data, element_sym);
+ spec->data.element = parse_field_spec(list_fields);
+ break;
+ }
+ }
+
+ return spec;
+}
+
+
+// -----------------------------------------------------------------------------
+// Serialization routines
+// -----------------------------------------------------------------------------
+
+
+// write_*(VALUE buf, ...) takes a value and adds it to a Ruby string buffer,
+// in network order
+static void write_byte(VALUE buf, int8_t val) {
+ rb_str_buf_cat(buf, (char*)&val, sizeof(int8_t));
+}
+
+static void write_i16(VALUE buf, int16_t val) {
+ int16_t net = (int16_t)htons(val);
+ rb_str_buf_cat(buf, (char*)&net, sizeof(int16_t));
+}
+
+static void write_i32(VALUE buf, int32_t val) {
+ int32_t net = (int32_t)htonl(val);
+ rb_str_buf_cat(buf, (char*)&net, sizeof(int32_t));
+}
+
+static void write_i64(VALUE buf, int64_t val) {
+ int64_t net = (int64_t)htonll(val);
+ rb_str_buf_cat(buf, (char*)&net, sizeof(int64_t));
+}
+
+static void write_double(VALUE buf, double dub) {
+ // Unfortunately, bitwise_cast doesn't work in C. Bad C!
+ union {
+ double f;
+ int64_t t;
+ } transfer;
+ transfer.f = dub;
+ write_i64(buf, transfer.t);
+}
+
+static void write_string(VALUE buf, char* str) {
+ int32_t len = strlen(str);
+ write_i32(buf, len);
+ rb_str_buf_cat2(buf, str);
+}
+
+// Some functions macro'd out because they're nops for the binary protocol
+// but placeholders were desired in case things change
+#define write_struct_begin(buf)
+#define write_struct_end(buf)
+
+static void write_field_begin(VALUE buf, char* name, int type, int fid) {
+#ifdef __DEBUG__
+ fprintf(stderr, "Writing field beginning: %s %d %d\n", name, type, fid);
+#endif
+
+ write_byte(buf, (int8_t)type);
+ write_i16(buf, (int16_t)fid);
+}
+
+#define write_field_end(buf)
+
+static void write_field_stop(VALUE buf) {
+ write_byte(buf, T_STOP);
+}
+
+static void write_map_begin(VALUE buf, int8_t ktype, int8_t vtype, int32_t sz) {
+ write_byte(buf, ktype);
+ write_byte(buf, vtype);
+ write_i32(buf, sz);
+}
+
+#define write_map_end(buf);
+
+static void write_list_begin(VALUE buf, int type, int sz) {
+ write_byte(buf, type);
+ write_i32(buf, sz);
+}
+
+#define write_list_end(buf)
+
+static void write_set_begin(VALUE buf, int type, int sz) {
+ write_byte(buf, type);
+ write_i32(buf, sz);
+}
+
+#define write_set_end(buf)
+
+static void binary_encoding(VALUE buf, VALUE obj, int type);
+
+// Handles container types: Map, Set, List
+static void write_container(VALUE buf, VALUE value, field_spec* spec) {
+ int sz, i;
+
+ switch(spec->type) {
+ case T_MAP: {
+ VALUE keys;
+ VALUE key;
+ VALUE val;
+
+ keys = rb_funcall(value, keys_id, 0);
+
+ sz = RARRAY(keys)->len;
+
+ write_map_begin(buf, spec->data.map->key->type, spec->data.map->value->type, sz);
+
+ for (i = 0; i < sz; i++) {
+ key = rb_ary_entry(keys, i);
+ val = rb_hash_aref(value, key);
+
+ if (IS_CONTAINER(spec->data.map->key->type)) {
+ write_container(buf, key, spec->data.map->key);
+ } else {
+ binary_encoding(buf, key, spec->data.map->key->type);
+ }
+
+ if (IS_CONTAINER(spec->data.map->value->type)) {
+ write_container(buf, val, spec->data.map->value);
+ } else {
+ binary_encoding(buf, val, spec->data.map->value->type);
+ }
+ }
+
+ write_map_end(buf);
+
+ break;
+ }
+
+ case T_LIST: {
+ sz = RARRAY(value)->len;
+
+ write_list_begin(buf, spec->data.element->type, sz);
+ for (i = 0; i < sz; ++i) {
+ if (IS_CONTAINER(spec->data.element->type)) {
+ write_container(buf, rb_ary_entry(value, i), spec->data.element);
+ } else {
+ binary_encoding(buf, rb_ary_entry(value, i), spec->data.element->type);
+ }
+ }
+ write_list_end(buf);
+ break;
+ }
+
+ case T_SET: {
+ VALUE items;
+
+ if (TYPE(value) == T_ARRAY) {
+ items = value;
+ } else {
+ if (rb_cSet == CLASS_OF(value)) {
+ items = rb_funcall(value, entries_id, 0);
+ } else {
+ Check_Type(value, T_HASH);
+ items = rb_funcall(value, keys_id, 0);
+ }
+ }
+
+ sz = RARRAY(items)->len;
+
+ write_set_begin(buf, spec->data.element->type, sz);
+
+ for (i = 0; i < sz; i++) {
+ if (IS_CONTAINER(spec->data.element->type)) {
+ write_container(buf, rb_ary_entry(items, i), spec->data.element);
+ } else {
+ binary_encoding(buf, rb_ary_entry(items, i), spec->data.element->type);
+ }
+ }
+
+ write_set_end(buf);
+ break;
+ }
+ }
+}
+
+// Takes the field id, data to be encoded, buffer and enclosing object
+// to be encoded. buf and obj passed as a ruby array for rb_hash_foreach.
+// TODO(kevinclark): See if they can be passed individually to avoid object
+// creation
+static int encode_field(VALUE fid, VALUE data, VALUE ary) {
+ field_spec *spec = parse_field_spec(data);
+
+ VALUE buf = rb_ary_entry(ary, 0);
+ VALUE obj = rb_ary_entry(ary, 1);
+ char name_buf[128];
+
+ name_buf[0] = '@';
+ strlcpy(&name_buf[1], spec->name, sizeof(name_buf) - 1);
+
+ VALUE value = rb_ivar_get(obj, rb_intern(name_buf));
+
+ if (Qnil == value) {
+ free_field_spec(spec);
+ return 0;
+ }
+
+ write_field_begin(buf, spec->name, spec->type, NUM2INT(fid));
+
+ if (IS_CONTAINER(spec->type)) {
+ write_container(buf, value, spec);
+ } else {
+ binary_encoding(buf, value, spec->type);
+ }
+ write_field_end(buf);
+
+ free_field_spec(spec);
+
+ return 0;
+}
+
+// -----------------------------------------------------------------------------
+// TFastBinaryProtocol's main encoding loop
+// -----------------------------------------------------------------------------
+
+static void binary_encoding(VALUE buf, VALUE obj, int type) {
+#ifdef __DEBUG__
+ rb_p(rb_str_new2("Encoding binary (buf, obj, type)"));
+ rb_p(rb_inspect(buf));
+ rb_p(rb_inspect(obj));
+ rb_p(rb_inspect(INT2FIX(type)));
+#endif
+
+ switch(type) {
+ case T_BOOL:
+ if RTEST(obj) {
+ write_byte(buf, 1);
+ }
+ else {
+ write_byte(buf, 0);
+ }
+
+ break;
+
+ case T_BYTE:
+ write_byte(buf, NUM2INT(obj));
+ break;
+
+ case T_I16:
+ write_i16(buf, NUM2INT(obj));
+ break;
+
+ case T_I32:
+ write_i32(buf, NUM2INT(obj));
+ break;
+
+ case T_I64:
+ write_i64(buf, rb_num2ll(obj));
+ break;
+
+ case T_DBL:
+ write_double(buf, NUM2DBL(obj));
+ break;
+
+ case T_STR:
+ write_string(buf, StringValuePtr(obj));
+ break;
+
+ case T_STRCT: {
+ // rb_hash_foreach has to take args as a ruby array
+ VALUE args = rb_ary_new3(2, buf, obj);
+ VALUE fields = rb_const_get(CLASS_OF(obj), fields_id);
+
+ write_struct_begin(buf);
+
+ rb_hash_foreach(fields, encode_field, args);
+
+ write_field_stop(buf);
+ write_struct_end(buf);
+ break;
+ }
+
+ default: {
+ rb_raise(rb_eNotImpError, "Unknown type for binary_encoding: %d", type);
+ }
+ }
+}
+
+// obj is always going to be a TSTRCT
+static VALUE tbpa_encode_binary(VALUE self, VALUE obj) {
+ VALUE buf = rb_str_buf_new(1024);
+ binary_encoding(buf, obj, T_STRCT);
+ return buf;
+}
+
+
+// -----------------------------------------------------------------------------
+// Read stuff
+// -----------------------------------------------------------------------------
+
+typedef struct {
+ char* name;
+ int8_t type;
+ int16_t id;
+} field_header;
+
+typedef struct {
+ int8_t key_type;
+ int8_t val_type;
+ int num_entries;
+} map_header;
+
+typedef struct {
+ int8_t type;
+ int num_elements;
+} list_header;
+
+typedef list_header set_header;
+
+typedef struct {
+ char* data;
+ int pos;
+ int len;
+ VALUE trans;
+} decode_buffer;
+
+typedef struct {
+ char* ptr;
+ int len;
+} thrift_string;
+
+#define read_struct_begin(buf)
+#define read_struct_end(buf)
+
+// This prototype is required to be able to run a call through rb_protect
+// which rescues from ruby exceptions
+static VALUE protectable_consume(VALUE args) {
+ VALUE trans = rb_ary_entry(args, 0);
+ VALUE size = rb_ary_entry(args, 1);
+
+ return rb_funcall(trans, consume_bang_id, 1, size);
+}
+
+// Clears size bytes from the transport's string buffer
+static bool consume(decode_buffer* buf, int32_t size) {
+ if (size != 0) {
+ VALUE ret;
+ VALUE args = rb_ary_new3(2, buf->trans, INT2FIX(size));
+ int status = 0;
+
+ ret = rb_protect(protectable_consume, args, &status);
+
+ if (status) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ // Nothing to consume, we're all good
+ return true;
+}
+
+// This prototype is required to be able to run a call through rb_protect
+// which rescues from ruby exceptions
+static VALUE protectable_borrow(VALUE args) {
+ VALUE trans = rb_ary_entry(args, 0);
+
+ switch(RARRAY(args)->len) {
+ case 1:
+ return rb_funcall(trans, borrow_id, 0);
+
+ case 2: {
+ VALUE size = rb_ary_entry(args, 1);
+ return rb_funcall(trans, borrow_id, 1, size);
+ }
+ }
+
+ return Qnil;
+}
+
+// Calls into the transport to get the available string buffer
+static bool borrow(decode_buffer* buf, int32_t size, VALUE* dst) {
+ int status = 0;
+ VALUE args;
+
+ if (size == 0) {
+ args = rb_ary_new3(1, buf->trans);
+ } else {
+ args = rb_ary_new3(2, buf->trans, INT2FIX(size));
+ }
+
+ *dst = rb_protect(protectable_borrow, args, &status);
+
+ return (status == 0);
+}
+
+// Refills the buffer by calling borrow. If buf->pos is nonzero that number of bytes
+// is cleared through consume.
+//
+// returns: 0 on success, non-zero on failure. On error buf is unchanged.
+static int fill_buffer(decode_buffer* buf, int32_t req_len) {
+ VALUE refill;
+
+ if (!consume(buf, buf->pos)) {
+ return -1;
+ }
+
+ if (!borrow(buf, req_len, &refill)) {
+ return -2;
+ }
+
+ buf->data = StringValuePtr(refill);
+ buf->len = RSTRING(refill)->len;
+ buf->pos = 0;
+
+ return 0;
+}
+
+
+// read_bytes pulls a number of bytes (size) from the buffer, refilling if needed,
+// and places them in dst. This should _always_ be used used when reading from the buffer
+// or buffered transports will be upset with you.
+static bool read_bytes(decode_buffer* buf, void* dst, size_t size) {
+ int avail = (buf->len - buf->pos);
+
+ if (size <= avail) {
+ memcpy(dst, buf->data + buf->pos, size);
+ buf->pos += size;
+ } else {
+
+ if (avail > 0) {
+ // Copy what we can
+ memcpy(dst, buf->data + buf->pos, avail);
+ buf->pos += avail;
+ }
+
+ if (fill_buffer(buf, size - avail) < 0) {
+ return false;
+ }
+
+ memcpy(dst + avail, buf->data, size - avail);
+ buf->pos += size - avail;
+ }
+
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+// Helpers for grabbing specific types from the buffer
+// -----------------------------------------------------------------------------
+
+static bool read_byte(decode_buffer* buf, int8_t* data) {
+ return read_bytes(buf, data, sizeof(int8_t));
+}
+
+static bool read_int16(decode_buffer* buf, int16_t* data) {
+ bool success = read_bytes(buf, data, sizeof(int16_t));
+ *data = ntohs(*data);
+
+ return success;
+}
+
+static bool read_int32(decode_buffer* buf, int32_t* data) {
+ bool success = read_bytes(buf, data, sizeof(int32_t));
+ *data = ntohl(*data);
+
+ return success;
+}
+
+static bool read_int64(decode_buffer* buf, int64_t* data) {
+ bool success = read_bytes(buf, data, sizeof(int64_t));
+ *data = ntohll(*data);
+
+ return success;
+}
+
+static bool read_double(decode_buffer* buf, double* data) {
+ return read_int64(buf, (int64_t*)data);
+}
+
+static bool read_string(decode_buffer* buf, VALUE* data) {
+ int len;
+
+ if (!read_int32(buf, &len)) {
+ return false;
+ }
+
+ if (buf->len - buf->pos >= len) {
+ *data = rb_str_new(buf->data + buf->pos, len);
+ buf->pos += len;
+ }
+ else {
+ char* str;
+
+ if ((str = (char*) malloc(len)) == NULL) {
+ return false;
+ }
+
+ if (!read_bytes(buf, str, len)) {
+ free(str);
+ return false;
+ }
+
+ *data = rb_str_new(str, len);
+
+ free(str);
+ }
+
+ return true;
+}
+
+static bool read_field_begin(decode_buffer* buf, field_header* header) {
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+ bzero(header, sizeof(field_header));
+#endif
+
+ header->name = NULL;
+
+ if (!read_byte(buf, &header->type)) {
+ return false;
+ }
+
+ if (header->type == T_STOP) {
+ header->id = 0;
+ } else {
+ if (!read_int16(buf, &header->id)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#define read_field_end(buf)
+
+static bool read_map_begin(decode_buffer* buf, map_header* header) {
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+ bzero(header, sizeof(map_header));
+#endif
+
+ return (read_byte(buf, &header->key_type) &&
+ read_byte(buf, &header->val_type) &&
+ read_int32(buf, &header->num_entries));
+}
+
+#define read_map_end(buf)
+
+static bool read_list_begin(decode_buffer* buf, list_header* header) {
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+ bzero(header, sizeof(list_header));
+#endif
+
+ if (!read_byte(buf, &header->type) || !read_int32(buf, &header->num_elements)) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+#define read_list_end(buf)
+
+#define read_set_begin read_list_begin
+#define read_set_end read_list_end
+
+
+// High level reader function with ruby type coercion
+static bool read_type(int type, decode_buffer* buf, VALUE* dst) {
+ switch(type) {
+ case T_BOOL: {
+ int8_t byte;
+
+ if (!read_byte(buf, &byte)) {
+ return false;
+ }
+
+ if (0 == byte) {
+ *dst = Qfalse;
+ } else {
+ *dst = Qtrue;
+ }
+
+ break;
+ }
+
+ case T_BYTE: {
+ int8_t byte;
+
+ if (!read_byte(buf, &byte)) {
+ return false;
+ }
+
+ *dst = INT2FIX(byte);
+ break;
+ }
+
+ case T_I16: {
+ int16_t i16;
+
+ if (!read_int16(buf, &i16)) {
+ return false;
+ }
+
+ *dst = INT2FIX(i16);
+ break;
+ }
+
+ case T_I32: {
+ int32_t i32;
+
+ if (!read_int32(buf, &i32)) {
+ return false;
+ }
+
+ *dst = INT2NUM(i32);
+ break;
+ }
+
+ case T_I64: {
+ int64_t i64;
+
+ if (!read_int64(buf, &i64)) {
+ return false;
+ }
+
+ *dst = rb_ll2inum(i64);
+ break;
+ }
+
+ case T_DBL: {
+ double dbl;
+
+ if (!read_double(buf, &dbl)) {
+ return false;
+ }
+
+ *dst = rb_float_new(dbl);
+ break;
+ }
+
+ case T_STR: {
+ VALUE str;
+
+ if (!read_string(buf, &str)) {
+ return false;
+ }
+
+ *dst = str;
+ break;
+ }
+ }
+
+ return true;
+}
+
+// TODO(kevinclark): Now that read_string does a malloc,
+// This maybe could be modified to avoid that, and the type coercion
+
+// Read the bytes but don't do anything with the value
+static bool skip_type(int type, decode_buffer* buf) {
+ switch (type) {
+ case T_STRCT:
+ read_struct_begin(buf);
+ while (true) {
+ field_header header;
+
+ if (!read_field_begin(buf, &header)) {
+ return false;
+ }
+
+ if (header.type == T_STOP) {
+ break;
+ }
+
+ if (!skip_type(header.type, buf)) {
+ return false;
+ }
+
+ read_field_end(buf);
+ }
+ read_struct_end(buf);
+
+ break;
+
+ case T_MAP: {
+ int i;
+ map_header header;
+
+ if (!read_map_begin(buf, &header)) {
+ return false;
+ }
+
+ for (i = 0; i < header.num_entries; ++i) {
+ if (!skip_type(header.key_type, buf)) {
+ return false;
+ }
+ if (!skip_type(header.val_type, buf)) {
+ return false;
+ }
+ }
+
+ read_map_end(buf);
+ break;
+ }
+
+ case T_SET: {
+ int i;
+ set_header header;
+
+ if (!read_set_begin(buf, &header)) {
+ return false;
+ }
+
+ for (i = 0; i < header.num_elements; ++i) {
+ if (!skip_type(header.type, buf)) {
+ return false;
+ }
+ }
+
+ read_set_end(buf);
+ break;
+ }
+
+ case T_LIST: {
+ int i;
+ list_header header;
+
+ if (!read_list_begin(buf, &header)) {
+ return false;
+ }
+
+ for (i = 0; i < header.num_elements; ++i) {
+ if (!skip_type(header.type, buf)) {
+ return false;
+ }
+ }
+
+ read_list_end(buf);
+ break;
+ }
+
+ default: {
+ VALUE v;
+ if (!read_type(type, buf, &v)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+static VALUE read_struct(VALUE obj, decode_buffer* buf);
+
+// Read the right thing from the buffer given the field spec
+// and return the ruby object
+static bool read_field(decode_buffer* buf, field_spec* spec, VALUE* dst) {
+ switch (spec->type) {
+ case T_STRCT: {
+ VALUE obj = rb_class_new_instance(0, NULL, spec->data.class);
+
+ *dst = read_struct(obj, buf);
+ break;
+ }
+
+ case T_MAP: {
+ map_header hdr;
+ VALUE hsh;
+ int i;
+
+ read_map_begin(buf, &hdr);
+ hsh = rb_hash_new();
+
+ for (i = 0; i < hdr.num_entries; ++i) {
+ VALUE key, val;
+
+ if (!read_field(buf, spec->data.map->key, &key)) {
+ return false;
+ }
+
+ if (!read_field(buf, spec->data.map->value, &val)) {
+ return false;
+ }
+
+ rb_hash_aset(hsh, key, val);
+ }
+
+ read_map_end(buf);
+
+ *dst = hsh;
+ break;
+ }
+
+ case T_LIST: {
+ list_header hdr;
+ VALUE arr, element;
+ int i;
+
+ read_list_begin(buf, &hdr);
+ arr = rb_ary_new2(hdr.num_elements);
+
+ for (i = 0; i < hdr.num_elements; ++i) {
+ if (!read_field(buf, spec->data.element, &element)) {
+ return false;
+ }
+
+ rb_ary_push(arr, element);
+ }
+
+ read_list_end(buf);
+
+ *dst = arr;
+ break;
+ }
+
+ case T_SET: {
+ VALUE items, item;
+ set_header hdr;
+ int i;
+
+ read_set_begin(buf, &hdr);
+ items = rb_ary_new2(hdr.num_elements);
+
+ for (i = 0; i < hdr.num_elements; ++i) {
+ if (!read_field(buf, spec->data.element, &item)) {
+ return false;
+ }
+
+ rb_ary_push(items, item);
+ }
+
+ *dst = rb_class_new_instance(1, &items, rb_cSet);
+ break;
+ }
+
+
+ default:
+ return read_type(spec->type, buf, dst);
+ }
+
+ return true;
+}
+
+static void handle_read_error() {
+ // If it was an exception, reraise
+ if (!NIL_P(ruby_errinfo)) {
+ rb_exc_raise(ruby_errinfo);
+ } else {
+ // Something else went wrong, no idea what would call this yet
+ // So far, the only thing to cause failures underneath is ruby
+ // exceptions. Follow up on this regularly -- Kevin Clark (TODO)
+ rb_raise(rb_eStandardError, "[BUG] Something went wrong in the field reading, but not a ruby exception");
+ }
+}
+
+// Fill in the instance variables in an object (thrift struct)
+// from the decode buffer
+static VALUE read_struct(VALUE obj, decode_buffer* buf) {
+ VALUE field;
+ field_header f_header;
+ VALUE value = Qnil;
+ VALUE fields = rb_const_get(CLASS_OF(obj), fields_id);
+ field_spec* spec;
+ char name_buf[128];
+
+ read_struct_begin(buf);
+
+ while(true) {
+ if (!read_field_begin(buf, &f_header)) {
+ handle_read_error();
+ }
+
+ if (T_STOP == f_header.type) {
+ break;
+ }
+
+ field = rb_hash_aref(fields, INT2FIX(f_header.id));
+
+ if (NIL_P(field)) {
+ if (!skip_type(f_header.type, buf)) {
+ handle_read_error();
+ return Qnil;
+ }
+ }
+ else {
+ spec = parse_field_spec(field);
+
+ if (spec->type != f_header.type) {
+ if (!skip_type(spec->type, buf)) {
+ free_field_spec(spec);
+ handle_read_error();
+ return Qnil;
+ }
+ } else {
+ // Read busted somewhere (probably borrow/consume), bail
+ if (!read_field(buf, spec, &value)) {
+ free_field_spec(spec);
+ handle_read_error();
+ return Qnil;
+ }
+
+ name_buf[0] = '@';
+ strlcpy(&name_buf[1], spec->name, sizeof(name_buf) - 1);
+
+ rb_iv_set(obj, name_buf, value);
+ }
+
+ free_field_spec(spec);
+ }
+
+ read_field_end(buf);
+ }
+
+ read_struct_end(buf);
+
+ return obj;
+}
+
+
+// Takes an object and transport, and decodes the values in the transport's
+// buffer to fill the object.
+static VALUE tbpa_decode_binary(VALUE self, VALUE obj, VALUE transport) {
+ decode_buffer buf;
+ VALUE ret_val;
+
+ buf.pos = 0; // This needs to be set so an arbitrary number of bytes isn't consumed
+ buf.trans = transport; // We need to hold this so the buffer can be refilled
+
+ if (fill_buffer(&buf, 0) < 0) {
+ handle_read_error();
+ return Qnil;
+ }
+
+#ifdef __DEBUG__
+ rb_p(rb_str_new2("Running decode binary with data:"));
+ rb_p(rb_inspect(rb_str_new2(buf.data)));
+#endif
+
+ ret_val = read_struct(obj, &buf);
+
+ // Consume whatever was read
+ consume(&buf, buf.pos);
+
+ return ret_val;
+}
+
+// -----------------------------------------------------------------------------
+// These methods are used by the thrift library and need to handled
+// seperately from encode and decode
+// -----------------------------------------------------------------------------
+
+// Read the message header and return it as a ruby array
+static VALUE tbpa_read_message_begin(VALUE self) {
+ decode_buffer buf;
+ int32_t version, seqid;
+ int8_t type;
+ VALUE name;
+
+ VALUE trans = rb_iv_get(self, "@trans");
+
+ buf.pos = 0; // This needs to be set so fill_buffer doesn't consume
+ buf.trans = trans; // We need to hold this so the buffer can be refilled
+
+
+ if (fill_buffer(&buf, 0) < 0 || !read_int32(&buf, &version)) {
+ // Consume whatever was read
+ consume(&buf, buf.pos);
+ handle_read_error();
+ return Qnil;
+ }
+
+ if ((version & VERSION_MASK) != VERSION_1) {
+ VALUE tprotocol_exception = rb_const_get(rb_cObject, rb_intern("TProtocolException"));
+ VALUE exception = rb_funcall(tprotocol_exception, rb_intern("new"), 2, rb_const_get(tprotocol_exception, rb_intern("BAD_VERSION")), rb_str_new2("Missing version identifier"));
+ rb_raise(exception, "");
+ }
+
+ type = version & 0x000000ff;
+
+ if (!read_string(&buf, &name) || !read_int32(&buf, &seqid)) {
+ // Consume whatever was read
+ consume(&buf, buf.pos);
+ handle_read_error();
+ return Qnil;
+ }
+
+ // Consume whatever was read
+ if (consume(&buf, buf.pos) < 0) {
+ handle_read_error();
+ return Qnil;
+ }
+
+ return rb_ary_new3(3, name, INT2FIX(type), INT2FIX(seqid));
+}
+
+void Init_binaryprotocolaccelerated()
+{
+ m_thrift = rb_const_get(rb_cObject, rb_intern("Thrift"));
+ VALUE class_tbinproto = rb_const_get(m_thrift, rb_intern("BinaryProtocol"));
+ class_tbpa = rb_define_class_under(m_thrift, "BinaryProtocolAccelerated", class_tbinproto);
+ type_sym = ID2SYM(rb_intern("type"));
+ class_sym = ID2SYM(rb_intern("class"));
+ key_sym = ID2SYM(rb_intern("key"));
+ value_sym = ID2SYM(rb_intern("value"));
+ name_sym = ID2SYM(rb_intern("name"));
+ fields_id = rb_intern("FIELDS");
+ element_sym = ID2SYM(rb_intern("element"));
+ consume_bang_id = rb_intern("consume!");
+ string_buffer_id = rb_intern("string_buffer");
+ borrow_id = rb_intern("borrow");
+ keys_id = rb_intern("keys");
+ entries_id = rb_intern("entries");
+ rb_cSet = rb_const_get(rb_cObject, rb_intern("Set"));
+
+ // For fast access
+ rb_define_method(class_tbpa, "encode_binary", tbpa_encode_binary, 1);
+ rb_define_method(class_tbpa, "decode_binary", tbpa_decode_binary, 2);
+ rb_define_method(class_tbpa, "read_message_begin", tbpa_read_message_begin, 0);
+
+}