#include "serializer.h" #include "constants.h" #include "utility.h" extern VALUE mRocketAMF; extern VALUE mRocketAMFExt; extern VALUE cSerializer; extern VALUE cStringIO; extern VALUE cDate; extern VALUE cDateTime; extern VALUE sym_class_name; extern VALUE sym_members; extern VALUE sym_externalizable; extern VALUE sym_dynamic; VALUE cArrayCollection; ID id_haskey; ID id_encode_amf; ID id_is_array_collection; ID id_use_array_collection; ID id_get_as_class_name; ID id_props_for_serialization; ID id_utc; ID id_to_f; ID id_is_integer; static VALUE ser0_serialize(VALUE self, VALUE obj); static VALUE ser3_serialize(VALUE self, VALUE obj); void ser_write_byte(AMF_SERIALIZER *ser, char byte) { char bytes[2] = {byte, '\0'}; rb_str_buf_cat(ser->stream, bytes, 1); } void ser_write_int(AMF_SERIALIZER *ser, int num) { char tmp[4]; int tmp_len; num &= 0x1fffffff; if (num < 0x80) { tmp_len = 1; tmp[0] = num; } else if (num < 0x4000) { tmp_len = 2; tmp[0] = (num >> 7 & 0x7f) | 0x80; tmp[1] = num & 0x7f; } else if (num < 0x200000) { tmp_len = 3; tmp[0] = (num >> 14 & 0x7f) | 0x80; tmp[1] = (num >> 7 & 0x7f) | 0x80; tmp[2] = num & 0x7f; } else if (num < 0x40000000) { tmp_len = 4; tmp[0] = (num >> 22 & 0x7f) | 0x80; tmp[1] = (num >> 15 & 0x7f) | 0x80; tmp[2] = (num >> 8 & 0x7f) | 0x80; tmp[3] = (num & 0xff); } else { rb_raise(rb_eRangeError, "int %d out of range", num); } rb_str_buf_cat(ser->stream, tmp, tmp_len); } void ser_write_uint16(AMF_SERIALIZER *ser, long num) { if(num > 0xffff) rb_raise(rb_eRangeError, "int %ld out of range", num); char tmp[2] = {(num >> 8) & 0xff, num & 0xff}; rb_str_buf_cat(ser->stream, tmp, 2); } void ser_write_uint32(AMF_SERIALIZER *ser, long num) { if(num > 0xffffffff) rb_raise(rb_eRangeError, "int %ld out of range", num); char tmp[4] = {(num >> 24) & 0xff, (num >> 16) & 0xff, (num >> 8) & 0xff, num & 0xff}; rb_str_buf_cat(ser->stream, tmp, 4); } void ser_write_double(AMF_SERIALIZER *ser, double num) { union aligned { double dval; char cval[8]; } d; const char *number = d.cval; d.dval = num; #ifdef WORDS_BIGENDIAN rb_str_buf_cat(ser->stream, number, 8); #else char netnum[8] = {number[7],number[6],number[5],number[4],number[3],number[2],number[1],number[0]}; rb_str_buf_cat(ser->stream, netnum, 8); #endif } void ser_get_string(VALUE obj, VALUE encode, char** str, long* len) { int type = TYPE(obj); if(type == T_STRING) { #ifdef HAVE_RB_STR_ENCODE if(encode == Qtrue) { rb_encoding *enc = rb_enc_get(obj); if (enc != rb_ascii8bit_encoding()) { rb_encoding *utf8 = rb_utf8_encoding(); if (enc != utf8) obj = rb_str_encode(obj, rb_enc_from_encoding(utf8), 0, Qnil); } } #endif *str = RSTRING_PTR(obj); *len = RSTRING_LEN(obj); } else if(type == T_SYMBOL) { *str = (char*)rb_id2name(SYM2ID(obj)); *len = strlen(*str); } else if(obj == Qnil) { *len = 0; } else { rb_raise(rb_eArgError, "Invalid type in ser_get_string: %d", type); } } /* * Write the given array in AMF0 notation */ static void ser0_write_array(VALUE self, VALUE ary) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); // Cache it st_add_direct(ser->obj_cache, ary, LONG2FIX(ser->obj_index)); ser->obj_index++; // Write it out long i, len = RARRAY_LEN(ary); ser_write_byte(ser, AMF0_STRICT_ARRAY_MARKER); ser_write_uint32(ser, len); for(i = 0; i < len; i++) { ser0_serialize(self, RARRAY_PTR(ary)[i]); } } /* * Supports writing strings and symbols. For hash keys, strings all have 16 bit * lengths, so writing a type marker is unnecessary. In that case the third * parameter should be set to Qfalse instead of Qtrue. */ static void ser0_write_string(AMF_SERIALIZER *ser, VALUE obj, VALUE write_marker) { // Extract char array and length from object char* str; long len; ser_get_string(obj, Qtrue, &str, &len); // Write string if(len > 0xffff) { if(write_marker == Qtrue) ser_write_byte(ser, AMF0_LONG_STRING_MARKER); ser_write_uint32(ser, len); } else { if(write_marker == Qtrue) ser_write_byte(ser, AMF0_STRING_MARKER); ser_write_uint16(ser, len); } rb_str_buf_cat(ser->stream, str, len); } /* * Hash iterator for object properties that writes the key and then serializes * the value */ static int ser0_hash_iter(VALUE key, VALUE val, const VALUE args[1]) { AMF_SERIALIZER *ser; Data_Get_Struct(args[0], AMF_SERIALIZER, ser); // Write key and value ser0_write_string(ser, key, Qfalse); // Technically incorrect if key length is longer than a 16 bit string, but if you run into that you're screwed anyways ser0_serialize(args[0], val); return ST_CONTINUE; } /* * Used for both hashes and objects. Takes the object and the props hash or Qnil, * which forces a call to the class mapper for props for serialization. Prop * sorting must be enabled by an explicit call to extconf.rb, so the tests will * not pass typically on Ruby 1.8. */ static void ser0_write_object(VALUE self, VALUE obj, VALUE props) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); // Cache it st_add_direct(ser->obj_cache, obj, LONG2FIX(ser->obj_index)); ser->obj_index++; // Make a request for props hash unless we already have it if(props == Qnil) { props = rb_funcall(ser->class_mapper, id_props_for_serialization, 1, obj); } // Write header VALUE class_name = rb_funcall(ser->class_mapper, id_get_as_class_name, 1, obj); if(class_name != Qnil) { ser_write_byte(ser, AMF0_TYPED_OBJECT_MARKER); ser0_write_string(ser, class_name, Qfalse); } else { ser_write_byte(ser, AMF0_OBJECT_MARKER); } // Write out data VALUE args[1] = {self}; #ifdef SORT_PROPS // Sort is required prior to Ruby 1.9 to pass all the tests, as Ruby 1.8 hashes don't store insert order VALUE sorted_props = rb_funcall(props, rb_intern("sort"), 0); long i, len = RARRAY_LEN(sorted_props); for(i = 0; i < len; i++) { VALUE pair = RARRAY_PTR(sorted_props)[i]; ser0_hash_iter(RARRAY_PTR(pair)[0], RARRAY_PTR(pair)[1], args); } #else rb_hash_foreach(props, ser0_hash_iter, (st_data_t)args); #endif ser_write_uint16(ser, 0); ser_write_byte(ser, AMF0_OBJECT_END_MARKER); } static void ser0_write_time(VALUE self, VALUE time) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); ser_write_byte(ser, AMF0_DATE_MARKER); // Write time time = rb_obj_dup(time); rb_funcall(time, id_utc, 0); double tmp_num = NUM2DBL(rb_funcall(time, id_to_f, 0)) * 1000; ser_write_double(ser, tmp_num); ser_write_uint16(ser, 0); // Time zone } static void ser0_write_date(VALUE self, VALUE date) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); ser_write_byte(ser, AMF0_DATE_MARKER); // Write time double tmp_num = rb_str_to_dbl(rb_funcall(date, rb_intern("strftime"), 1, rb_str_new2("%Q")), Qfalse); ser_write_double(ser, tmp_num); ser_write_uint16(ser, 0); // Time zone } /* * Serializes the object to a string and returns that string */ static VALUE ser0_serialize(VALUE self, VALUE obj) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); int type = TYPE(obj); VALUE klass = Qnil; if(type == T_OBJECT || type == T_DATA) { klass = CLASS_OF(obj); } VALUE obj_index; if(st_lookup(ser->obj_cache, obj, &obj_index)) { ser_write_byte(ser, AMF0_REFERENCE_MARKER); ser_write_uint16(ser, FIX2LONG(obj_index)); } else if(rb_respond_to(obj, id_encode_amf)) { rb_funcall(obj, id_encode_amf, 1, self); } else if(type == T_STRING || type == T_SYMBOL) { ser0_write_string(ser, obj, Qtrue); } else if(rb_obj_is_kind_of(obj, rb_cNumeric)) { ser_write_byte(ser, AMF0_NUMBER_MARKER); ser_write_double(ser, RFLOAT_VALUE(rb_Float(obj))); } else if(type == T_NIL) { ser_write_byte(ser, AMF0_NULL_MARKER); } else if(type == T_TRUE || type == T_FALSE) { ser_write_byte(ser, AMF0_BOOLEAN_MARKER); ser_write_byte(ser, type == T_TRUE ? 1 : 0); } else if(type == T_ARRAY) { ser0_write_array(self, obj); } else if(klass == rb_cTime) { ser0_write_time(self, obj); } else if(klass == cDate || klass == cDateTime) { ser0_write_date(self, obj); } else if(type == T_HASH || type == T_OBJECT) { ser0_write_object(self, obj, Qnil); } return ser->stream; } /* * Writes an AMF3 style string. Accepts strings, symbols, and nil, and handles * all the necessary encoding and caching. */ static void ser3_write_utf8vr(AMF_SERIALIZER *ser, VALUE obj) { // Extract char array and length from object char* str; long len; ser_get_string(obj, Qtrue, &str, &len); // Write string VALUE str_index; if(len == 0) { ser_write_byte(ser, AMF3_EMPTY_STRING); } else if(st_lookup(ser->str_cache, (st_data_t)str, &str_index)) { ser_write_int(ser, FIX2INT(str_index) << 1); } else { st_add_direct(ser->str_cache, (st_data_t)strdup(str), LONG2FIX(ser->str_index)); ser->str_index++; ser_write_int(ser, ((int)len) << 1 | 1); rb_str_buf_cat(ser->stream, str, len); } } /* * Writes Numeric conforming object using AMF3 notation */ static void ser3_write_numeric(AMF_SERIALIZER *ser, VALUE num) { // Is it an integer in range? if(rb_funcall(num, id_is_integer, 0) == Qtrue) { // It's an integer internally, so now we need to check if it's in range VALUE int_obj = rb_Integer(num); if(TYPE(int_obj) == T_FIXNUM) { long long_val = FIX2LONG(int_obj); if(long_val < MIN_INTEGER || long_val > MAX_INTEGER) { // Outside range, but we have a value already, so just cast to double ser_write_byte(ser, AMF3_DOUBLE_MARKER); ser_write_double(ser, (double)long_val); } else { // Inside valid integer range ser_write_byte(ser, AMF3_INTEGER_MARKER); ser_write_int(ser, (int)long_val); } return; } } // It's either not an integer or out of range, so write as a double ser_write_byte(ser, AMF3_DOUBLE_MARKER); ser_write_double(ser, RFLOAT_VALUE(rb_Float(num))); } /* * Writes the given array using AMF3 notation */ static void ser3_write_array(VALUE self, VALUE ary) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); // Is it an array collection? VALUE is_ac = Qfalse; if(rb_respond_to(ary, id_is_array_collection)) { is_ac = rb_funcall(ary, id_is_array_collection, 0); } else { is_ac = rb_funcall(ser->class_mapper, id_use_array_collection, 0); } // Write type marker ser_write_byte(ser, is_ac ? AMF3_OBJECT_MARKER : AMF3_ARRAY_MARKER); // Write object ref, or cache it VALUE obj_index; if(st_lookup(ser->obj_cache, ary, &obj_index)) { ser_write_int(ser, FIX2INT(obj_index) << 1); return; } else { st_add_direct(ser->obj_cache, ary, LONG2FIX(ser->obj_index)); ser->obj_index++; if(is_ac) ser->obj_index++; // The array collection source array } // Write out traits and array marker if it's an array collection if(is_ac) { VALUE trait_index; char array_collection_name[34] = "flex.messaging.io.ArrayCollection"; if(st_lookup(ser->trait_cache, (st_data_t)array_collection_name, &trait_index)) { ser_write_int(ser, FIX2INT(trait_index) << 2 | 0x01); } else { st_add_direct(ser->trait_cache, (st_data_t)strdup(array_collection_name), LONG2FIX(ser->trait_index)); ser->trait_index++; ser_write_byte(ser, 0x07); // Trait header ser3_write_utf8vr(ser, rb_str_new2(array_collection_name)); } ser_write_byte(ser, AMF3_ARRAY_MARKER); } // Write header int header = ((int)RARRAY_LEN(ary)) << 1 | 1; ser_write_int(ser, header); ser_write_byte(ser, AMF3_CLOSE_DYNAMIC_ARRAY); // Write contents long i, len = RARRAY_LEN(ary); for(i = 0; i < len; i++) { ser3_serialize(self, RARRAY_PTR(ary)[i]); } } /* * AMF3 property hash write iterator. Checks the args->extra hash, if given, * and skips properties that are keys in that hash. */ static int ser3_hash_iter(VALUE key, VALUE val, const VALUE args[2]) { AMF_SERIALIZER *ser; Data_Get_Struct(args[0], AMF_SERIALIZER, ser); if(args[1] == Qnil || rb_funcall(args[1], id_haskey, 1, key) == Qfalse) { // Write key and value ser3_write_utf8vr(ser, key); ser3_serialize(args[0], val); } return ST_CONTINUE; } /* * Used for both hashes and objects. Takes the object and the props hash or Qnil, * which forces a call to the class mapper for props for serialization. Prop * sorting must be enabled by an explicit call to extconf.rb, so the tests will * not pass typically on Ruby 1.8. If you need to have specific traits, you can * also pass that in, or pass Qnil to use the default traits - dynamic with no * defined members. */ static void ser3_write_object(VALUE self, VALUE obj, VALUE props, VALUE traits) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); long i; // Write type marker ser_write_byte(ser, AMF3_OBJECT_MARKER); // Write object ref, or cache it VALUE obj_index; if(st_lookup(ser->obj_cache, obj, &obj_index)) { ser_write_int(ser, FIX2INT(obj_index) << 1); return; } else { st_add_direct(ser->obj_cache, obj, LONG2FIX(ser->obj_index)); ser->obj_index++; } // Extract traits data, or use defaults VALUE is_default = Qfalse; VALUE class_name = Qnil; VALUE members = Qnil; long members_len = 0; VALUE dynamic = Qtrue; VALUE externalizable = Qfalse; if(traits == Qnil) { class_name = rb_funcall(ser->class_mapper, id_get_as_class_name, 1, obj); if(class_name == Qnil) is_default = Qtrue; } else { class_name = rb_hash_aref(traits, sym_class_name); members = rb_hash_aref(traits, sym_members); if(members != Qnil) members_len = RARRAY_LEN(members); dynamic = rb_hash_aref(traits, sym_dynamic); externalizable = rb_hash_aref(traits, sym_externalizable); } // Handle trait caching int did_ref = 0; VALUE trait_index; if(is_default == Qtrue || class_name != Qnil) { const char *ref_class_name = is_default == Qtrue ? "__default__" : RSTRING_PTR(class_name); if(st_lookup(ser->trait_cache, (st_data_t)ref_class_name, &trait_index)) { ser_write_int(ser, FIX2INT(trait_index) << 2 | 0x01); did_ref = 1; } else { st_add_direct(ser->trait_cache, (st_data_t)strdup(ref_class_name), LONG2FIX(ser->trait_index)); ser->trait_index++; } } // Write traits outs if didn't write reference if(!did_ref) { // Write out trait header int header = 0x03; if(dynamic == Qtrue) header |= 0x02 << 2; if(externalizable == Qtrue) header |= 0x01 << 2; header |= ((int)members_len) << 4; ser_write_int(ser, header); // Write class name ser3_write_utf8vr(ser, class_name); // Write out members for(i = 0; i < members_len; i++) { ser3_write_utf8vr(ser, RARRAY_PTR(members)[i]); } } // Raise exception if marked externalizable if(externalizable == Qtrue) { rb_funcall(obj, rb_intern("write_external"), 1, self); return; } // Make a request for props hash unless we already have it if(props == Qnil) { props = rb_funcall(ser->class_mapper, id_props_for_serialization, 1, obj); } // Write sealed members VALUE skipped_members = members_len ? rb_hash_new() : Qnil; for(i = 0; i < members_len; i++) { ser3_serialize(self, rb_hash_aref(props, RARRAY_PTR(members)[i])); rb_hash_aset(skipped_members, RARRAY_PTR(members)[i], Qtrue); } // Write dynamic properties if(dynamic == Qtrue) { VALUE args[2] = {self, skipped_members}; #ifdef SORT_PROPS // Sort is required prior to Ruby 1.9 to pass all the tests, as Ruby 1.8 hashes don't store insert order VALUE sorted_props = rb_funcall(props, rb_intern("sort"), 0); for(i = 0; i < RARRAY_LEN(sorted_props); i++) { VALUE pair = RARRAY_PTR(sorted_props)[i]; ser3_hash_iter(RARRAY_PTR(pair)[0], RARRAY_PTR(pair)[1], args); } #else rb_hash_foreach(props, ser3_hash_iter, (st_data_t)args); #endif ser_write_byte(ser, AMF3_CLOSE_DYNAMIC_OBJECT); } } static void ser3_write_time(VALUE self, VALUE time_obj) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); ser_write_byte(ser, AMF3_DATE_MARKER); // Write object ref, or cache it VALUE obj_index; if(st_lookup(ser->obj_cache, time_obj, &obj_index)) { ser_write_int(ser, FIX2INT(obj_index) << 1); return; } else { st_add_direct(ser->obj_cache, time_obj, LONG2FIX(ser->obj_index)); ser->obj_index++; } // Write time ser_write_byte(ser, AMF3_NULL_MARKER); // Ref header time_obj = rb_obj_dup(time_obj); rb_funcall(time_obj, id_utc, 0); double tmp_num = NUM2DBL(rb_funcall(time_obj, id_to_f, 0)) * 1000; ser_write_double(ser, tmp_num); } static void ser3_write_date(VALUE self, VALUE date) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); ser_write_byte(ser, AMF3_DATE_MARKER); // Write object ref, or cache it VALUE obj_index; if(st_lookup(ser->obj_cache, date, &obj_index)) { ser_write_int(ser, FIX2INT(obj_index) << 1); return; } else { st_add_direct(ser->obj_cache, date, LONG2FIX(ser->obj_index)); ser->obj_index++; } // Write time ser_write_byte(ser, AMF3_NULL_MARKER); // Ref header double tmp_num = rb_str_to_dbl(rb_funcall(date, rb_intern("strftime"), 1, rb_str_new2("%Q")), Qfalse); ser_write_double(ser, tmp_num); } static void ser3_write_byte_array(VALUE self, VALUE ba) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); ser_write_byte(ser, AMF3_BYTE_ARRAY_MARKER); // Write object ref, or cache it VALUE obj_index; if(st_lookup(ser->obj_cache, ba, &obj_index)) { ser_write_int(ser, FIX2INT(obj_index) << 1); return; } else { st_add_direct(ser->obj_cache, ba, LONG2FIX(ser->obj_index)); ser->obj_index++; } // Write byte array VALUE str = rb_funcall(ba, rb_intern("string"), 0); int len = (int)(RSTRING_LEN(str) << 1); // Explicitly cast to int to avoid compiler warning ser_write_int(ser, len | 1); rb_str_buf_cat(ser->stream, RSTRING_PTR(str), RSTRING_LEN(str)); } /* * Serializes the object to a string and returns that string */ static VALUE ser3_serialize(VALUE self, VALUE obj) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); int type = TYPE(obj); VALUE klass = Qnil; if(type == T_OBJECT || type == T_DATA || type == T_ARRAY) { klass = CLASS_OF(obj); } if(rb_respond_to(obj, id_encode_amf)) { rb_funcall(obj, id_encode_amf, 1, self); } else if(type == T_STRING || type == T_SYMBOL) { ser_write_byte(ser, AMF3_STRING_MARKER); ser3_write_utf8vr(ser, obj); } else if(rb_obj_is_kind_of(obj, rb_cNumeric)) { ser3_write_numeric(ser, obj); } else if(type == T_NIL) { ser_write_byte(ser, AMF3_NULL_MARKER); } else if(type == T_TRUE) { ser_write_byte(ser, AMF3_TRUE_MARKER); } else if(type == T_FALSE) { ser_write_byte(ser, AMF3_FALSE_MARKER); } else if(type == T_ARRAY) { ser3_write_array(self, obj); } else if(type == T_HASH) { ser3_write_object(self, obj, Qnil, Qnil); } else if(klass == rb_cTime) { ser3_write_time(self, obj); } else if(klass == cDate || klass == cDateTime) { ser3_write_date(self, obj); } else if(klass == cStringIO) { ser3_write_byte_array(self, obj); } else if(type == T_OBJECT) { ser3_write_object(self, obj, Qnil, Qnil); } return ser->stream; } /* * Mark ruby objects for GC */ static void ser_mark(AMF_SERIALIZER *ser) { if(!ser) return; rb_gc_mark(ser->class_mapper); rb_gc_mark(ser->stream); } /* * Free cache tables, stream and the struct itself */ int ser_free_strtable_key(st_data_t key, st_data_t value, st_data_t ignored) { xfree((void *)key); return ST_DELETE; } inline void ser_free_cache(AMF_SERIALIZER *ser) { if(ser->str_cache) { st_foreach(ser->str_cache, ser_free_strtable_key, 0); st_free_table(ser->str_cache); ser->str_cache = NULL; } if(ser->trait_cache) { st_foreach(ser->trait_cache, ser_free_strtable_key, 0); st_free_table(ser->trait_cache); ser->trait_cache = NULL; } if(ser->obj_cache) { st_free_table(ser->obj_cache); ser->obj_cache = NULL; } } static void ser_free(AMF_SERIALIZER *ser) { ser_free_cache(ser); xfree(ser); } /* * Create new struct and wrap with class */ static VALUE ser_alloc(VALUE klass) { // Allocate struct AMF_SERIALIZER *ser = ALLOC(AMF_SERIALIZER); memset(ser, 0, sizeof(AMF_SERIALIZER)); return Data_Wrap_Struct(klass, ser_mark, ser_free, ser); } /* * Initializer */ static VALUE ser_initialize(VALUE self, VALUE class_mapper) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); ser->class_mapper = class_mapper; ser->depth = 0; ser->stream = rb_str_buf_new(0); return self; } /* * call-seq: * ser.version => int * * Returns the serializer version number, so that a custom encode_amf method * knows which version to encode for */ static VALUE ser_version(VALUE self) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); return INT2FIX(ser->version); } /* * call-seq: * ser.stream => string * * Returns the string that the serializer is writing to */ static VALUE ser_stream(VALUE self) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); return ser->stream; } /* * call-seq: * ser.serialize(amf_ver, obj) => string * * Serialize the given object to the current stream and returns the stream */ VALUE ser_serialize(VALUE self, VALUE ver, VALUE obj) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); // Process version int int_ver = FIX2INT(ver); if(int_ver != 0 && int_ver != 3) rb_raise(rb_eArgError, "unsupported version %d", int_ver); ser->version = int_ver; // Initialize caches if(ser->depth == 0) { ser->obj_cache = st_init_numtable(); ser->obj_index = 0; if(ser->version == 3) { ser->str_cache = st_init_strtable(); ser->str_index = 0; ser->trait_cache = st_init_strtable(); ser->trait_index = 0; } } ser->depth++; // Perform serialization if(ser->version == 0) { ser0_serialize(self, obj); } else { ser3_serialize(self, obj); } // Clean up ser->depth--; if(ser->depth == 0) ser_free_cache(ser); return ser->stream; } /* * call-seq: * ser.write_array(ary) => ser * * Serializes the given array to the serializer stream */ static VALUE ser_write_array(VALUE self, VALUE ary) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); if(ser->version == 0) { ser0_write_array(self, ary); } else { ser3_write_array(self, ary); } return self; } /* * call-seq: * ser.write_object(obj, props=nil) => ser * ser.write_object(obj, props=nil, traits=nil) => ser * * Serializes the given object or hash to the serializer stream using * the proper serializer version. If given a props hash, uses that * instead of using the class mapper to calculate it. If given a traits * hash for AMF3, uses that instead of the default dynamic traits with * the mapped class name. */ static VALUE ser_write_object(int argc, VALUE *argv, VALUE self) { AMF_SERIALIZER *ser; Data_Get_Struct(self, AMF_SERIALIZER, ser); // Check args and call implementation VALUE obj; VALUE props = Qnil; VALUE traits = Qnil; if(ser->version == 0) { rb_scan_args(argc, argv, "11", &obj, &props); ser0_write_object(self, obj, props); } else { rb_scan_args(argc, argv, "12", &obj, &props, &traits); ser3_write_object(self, obj, props, traits); } return self; } void Init_rocket_amf_serializer() { // Define Serializer cSerializer = rb_define_class_under(mRocketAMFExt, "Serializer", rb_cObject); rb_define_alloc_func(cSerializer, ser_alloc); rb_define_method(cSerializer, "initialize", ser_initialize, 1); rb_define_method(cSerializer, "version", ser_version, 0); rb_define_method(cSerializer, "stream", ser_stream, 0); rb_define_method(cSerializer, "serialize", ser_serialize, 2); rb_define_method(cSerializer, "write_array", ser_write_array, 1); rb_define_method(cSerializer, "write_object", ser_write_object, -1); // Get refs to commonly used symbols and ids id_haskey = rb_intern("has_key?"); id_encode_amf = rb_intern("encode_amf"); id_is_array_collection = rb_intern("is_array_collection?"); id_use_array_collection = rb_intern("use_array_collection"); id_get_as_class_name = rb_intern("get_as_class_name"); id_props_for_serialization = rb_intern("props_for_serialization"); id_utc = rb_intern("utc"); id_to_f = rb_intern("to_f"); id_is_integer = rb_intern("integer?"); }