| // 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 (!NIL_P(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, size_t len) { | 
 |   write_i32(buf, len); | 
 |   rb_str_buf_cat(buf, str, len); | 
 | } | 
 |  | 
 | // 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; | 
 |  | 
 |       Check_Type(value, T_HASH); | 
 |        | 
 |       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: { | 
 |       Check_Type(value, T_ARRAY); | 
 |  | 
 |       sz = RARRAY(value)->len; | 
 |        | 
 |       write_list_begin(buf, spec->data.element->type, sz); | 
 |       for (i = 0; i < sz; ++i) { | 
 |         VALUE val = rb_ary_entry(value, i); | 
 |         if (IS_CONTAINER(spec->data.element->type)) { | 
 |           write_container(buf, val, spec->data.element); | 
 |         } else { | 
 |           binary_encoding(buf, val, 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++) { | 
 |         VALUE val = rb_ary_entry(items, i); | 
 |         if (IS_CONTAINER(spec->data.element->type)) { | 
 |           write_container(buf, val, spec->data.element); | 
 |         } else { | 
 |           binary_encoding(buf, val, 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 (NIL_P(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: { | 
 |       // make sure to call StringValuePtr before calling RSTRING | 
 |       char *ptr = StringValuePtr(obj); | 
 |       write_string(buf, ptr, RSTRING(obj)->len); | 
 |       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(m_thrift, rb_intern("ProtocolException")); | 
 |     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_exc_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); | 
 |    | 
 | } |