| // 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); |
| |
| } |