impress/vendor/gems/RocketAMF-1.0.0/ext/rocketamf_ext/serializer.c
Emi Matchu b1f06029f8 Moderize RocketAMF C types to fix build error
I'm not sure if this is a Mac-only problem or what, but we were getting incompatible-function-pointer errors when trying to build the RocketAMF C extensions. This fixes that! (Maybe it's like, Mac-only but as of Ruby 3.4 in specific? We're running RocketAMF in production on Ruby 3.4 right now without this. Shrug.)
2025-10-30 02:45:56 +00:00

834 lines
26 KiB
C

#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;
}
static 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?");
}