RocketAMF-ouvrages 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/README.rdoc +47 -0
  2. data/Rakefile +59 -0
  3. data/RocketAMF.gemspec +20 -0
  4. data/benchmark.rb +74 -0
  5. data/ext/rocketamf_ext/class_mapping.c +484 -0
  6. data/ext/rocketamf_ext/constants.h +52 -0
  7. data/ext/rocketamf_ext/deserializer.c +770 -0
  8. data/ext/rocketamf_ext/deserializer.h +27 -0
  9. data/ext/rocketamf_ext/extconf.rb +18 -0
  10. data/ext/rocketamf_ext/remoting.c +184 -0
  11. data/ext/rocketamf_ext/rocketamf_ext.c +38 -0
  12. data/ext/rocketamf_ext/serializer.c +834 -0
  13. data/ext/rocketamf_ext/serializer.h +29 -0
  14. data/ext/rocketamf_ext/utility.h +4 -0
  15. data/lib/rocketamf.rb +216 -0
  16. data/lib/rocketamf/class_mapping.rb +237 -0
  17. data/lib/rocketamf/constants.rb +50 -0
  18. data/lib/rocketamf/ext.rb +28 -0
  19. data/lib/rocketamf/extensions.rb +22 -0
  20. data/lib/rocketamf/pure.rb +24 -0
  21. data/lib/rocketamf/pure/deserializer.rb +455 -0
  22. data/lib/rocketamf/pure/io_helpers.rb +94 -0
  23. data/lib/rocketamf/pure/remoting.rb +117 -0
  24. data/lib/rocketamf/pure/serializer.rb +474 -0
  25. data/lib/rocketamf/remoting.rb +196 -0
  26. data/lib/rocketamf/values/messages.rb +214 -0
  27. data/lib/rocketamf/values/typed_hash.rb +13 -0
  28. data/spec/class_mapping_spec.rb +110 -0
  29. data/spec/deserializer_spec.rb +449 -0
  30. data/spec/fast_class_mapping_spec.rb +144 -0
  31. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  32. data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
  33. data/spec/fixtures/objects/amf0-date.bin +0 -0
  34. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  35. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  36. data/spec/fixtures/objects/amf0-null.bin +1 -0
  37. data/spec/fixtures/objects/amf0-number.bin +0 -0
  38. data/spec/fixtures/objects/amf0-object.bin +0 -0
  39. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  40. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  41. data/spec/fixtures/objects/amf0-string.bin +0 -0
  42. data/spec/fixtures/objects/amf0-time.bin +0 -0
  43. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  44. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  45. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  46. data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
  47. data/spec/fixtures/objects/amf3-0.bin +0 -0
  48. data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
  49. data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
  50. data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
  51. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  52. data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
  53. data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
  54. data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
  55. data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
  56. data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
  57. data/spec/fixtures/objects/amf3-date.bin +0 -0
  58. data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
  59. data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
  60. data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
  61. data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
  62. data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
  63. data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
  64. data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
  65. data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
  66. data/spec/fixtures/objects/amf3-false.bin +1 -0
  67. data/spec/fixtures/objects/amf3-float.bin +0 -0
  68. data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
  69. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  70. data/spec/fixtures/objects/amf3-large-max.bin +0 -0
  71. data/spec/fixtures/objects/amf3-large-min.bin +0 -0
  72. data/spec/fixtures/objects/amf3-max.bin +1 -0
  73. data/spec/fixtures/objects/amf3-min.bin +0 -0
  74. data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
  75. data/spec/fixtures/objects/amf3-null.bin +1 -0
  76. data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
  77. data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
  78. data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
  79. data/spec/fixtures/objects/amf3-string.bin +1 -0
  80. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  81. data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
  82. data/spec/fixtures/objects/amf3-true.bin +1 -0
  83. data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
  84. data/spec/fixtures/objects/amf3-vector-double.bin +0 -0
  85. data/spec/fixtures/objects/amf3-vector-int.bin +0 -0
  86. data/spec/fixtures/objects/amf3-vector-object.bin +0 -0
  87. data/spec/fixtures/objects/amf3-vector-uint.bin +0 -0
  88. data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
  89. data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
  90. data/spec/fixtures/objects/amf3-xml.bin +1 -0
  91. data/spec/fixtures/request/acknowledge-response.bin +0 -0
  92. data/spec/fixtures/request/amf0-error-response.bin +0 -0
  93. data/spec/fixtures/request/blaze-response.bin +0 -0
  94. data/spec/fixtures/request/commandMessage.bin +0 -0
  95. data/spec/fixtures/request/flex-request.bin +0 -0
  96. data/spec/fixtures/request/multiple-simple-request.bin +0 -0
  97. data/spec/fixtures/request/remotingMessage.bin +0 -0
  98. data/spec/fixtures/request/simple-request.bin +0 -0
  99. data/spec/fixtures/request/simple-response.bin +0 -0
  100. data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
  101. data/spec/messages_spec.rb +39 -0
  102. data/spec/remoting_spec.rb +196 -0
  103. data/spec/serializer_spec.rb +503 -0
  104. data/spec/spec_helper.rb +55 -0
  105. metadata +167 -0
@@ -0,0 +1,27 @@
1
+ #include <ruby.h>
2
+ #ifdef HAVE_RB_STR_ENCODE
3
+ #include <ruby/encoding.h>
4
+ #endif
5
+
6
+ typedef struct {
7
+ int version;
8
+ VALUE class_mapper;
9
+ VALUE src;
10
+ char* stream;
11
+ unsigned long pos;
12
+ unsigned long size;
13
+ VALUE obj_cache;
14
+ VALUE str_cache;
15
+ VALUE trait_cache;
16
+ } AMF_DESERIALIZER;
17
+
18
+ char des_read_byte(AMF_DESERIALIZER *des);
19
+ int des_read_uint16(AMF_DESERIALIZER *des);
20
+ unsigned int des_read_uint32(AMF_DESERIALIZER *des);
21
+ double des_read_double(AMF_DESERIALIZER *des);
22
+ int des_read_int(AMF_DESERIALIZER *des);
23
+ VALUE des_read_string(AMF_DESERIALIZER *des, unsigned int len);
24
+ VALUE des_read_sym(AMF_DESERIALIZER *des, unsigned int len);
25
+ void des_set_src(AMF_DESERIALIZER *des, VALUE src);
26
+
27
+ VALUE des_deserialize(VALUE self, VALUE ver, VALUE src);
@@ -0,0 +1,18 @@
1
+ require 'mkmf'
2
+
3
+ # Disable the native extension by creating an empty Makefile on JRuby
4
+ if defined? JRUBY_VERSION
5
+ message "Generating phony Makefile for JRuby so the gem installs"
6
+ mfile = File.join(File.dirname(__FILE__), 'Makefile')
7
+ File.open(mfile, 'w') {|f| f.write dummy_makefile(File.dirname(__FILE__)) }
8
+ exit 0
9
+ end
10
+
11
+ if enable_config("sort-props", false)
12
+ $defs.push("-DSORT_PROPS") unless $defs.include? "-DSORT_PROPS"
13
+ end
14
+ have_func('rb_str_encode')
15
+
16
+ $CFLAGS += " -Wall"
17
+
18
+ create_makefile('rocketamf_ext')
@@ -0,0 +1,184 @@
1
+ #include "deserializer.h"
2
+ #include "serializer.h"
3
+ #include "constants.h"
4
+
5
+ extern VALUE mRocketAMF;
6
+ extern VALUE mRocketAMFExt;
7
+ extern VALUE cDeserializer;
8
+ extern VALUE cSerializer;
9
+ VALUE cRocketAMFHeader;
10
+ VALUE cRocketAMFMessage;
11
+ VALUE cRocketAMFAbstractMessage;
12
+ ID id_amf_version;
13
+ ID id_headers;
14
+ ID id_messages;
15
+ ID id_data;
16
+
17
+ /*
18
+ * call-seq:
19
+ * env.populate_from_stream(stream, class_mapper=nil)
20
+ *
21
+ * Included into RocketAMF::Envelope, this method handles deserializing an AMF
22
+ * request/response into the envelope
23
+ */
24
+ static VALUE env_populate_from_stream(int argc, VALUE *argv, VALUE self) {
25
+ static VALUE cClassMapper = 0;
26
+ if(cClassMapper == 0) cClassMapper = rb_const_get(mRocketAMF, rb_intern("ClassMapper"));
27
+
28
+ // Parse args
29
+ VALUE src;
30
+ VALUE class_mapper;
31
+ rb_scan_args(argc, argv, "11", &src, &class_mapper);
32
+ if(class_mapper == Qnil) class_mapper = rb_class_new_instance(0, NULL, cClassMapper);
33
+
34
+ // Create AMF0 deserializer
35
+ VALUE args[3];
36
+ args[0] = class_mapper;
37
+ VALUE des_rb = rb_class_new_instance(1, args, cDeserializer);
38
+ AMF_DESERIALIZER *des;
39
+ Data_Get_Struct(des_rb, AMF_DESERIALIZER, des);
40
+ des_set_src(des, src);
41
+
42
+ // Read amf version
43
+ int amf_ver = des_read_uint16(des);
44
+
45
+ // Read headers
46
+ VALUE headers = rb_hash_new();
47
+ int header_cnt = des_read_uint16(des);
48
+ int i;
49
+ for(i = 0; i < header_cnt; i++) {
50
+ VALUE name = des_read_string(des, des_read_uint16(des));
51
+ VALUE must_understand = des_read_byte(des) != 0 ? Qtrue : Qfalse;
52
+ des_read_uint32(des); // Length is ignored
53
+ VALUE data = des_deserialize(des_rb, INT2FIX(0), Qnil);
54
+
55
+ args[0] = name;
56
+ args[1] = must_understand;
57
+ args[2] = data;
58
+ rb_hash_aset(headers, name, rb_class_new_instance(3, args, cRocketAMFHeader));
59
+ }
60
+
61
+ // Read messages
62
+ VALUE messages = rb_ary_new();
63
+ int message_cnt = des_read_uint16(des);
64
+ for(i = 0; i < message_cnt; i++) {
65
+ VALUE target_uri = des_read_string(des, des_read_uint16(des));
66
+ VALUE response_uri = des_read_string(des, des_read_uint16(des));
67
+ des_read_uint32(des); // Length is ignored
68
+ VALUE data = des_deserialize(des_rb, INT2FIX(0), Qnil);
69
+
70
+ // If they're using the flex remoting APIs, remove array wrapper
71
+ if(TYPE(data) == T_ARRAY && RARRAY_LEN(data) == 1 && rb_obj_is_kind_of(RARRAY_PTR(data)[0], cRocketAMFAbstractMessage) == Qtrue) {
72
+ data = RARRAY_PTR(data)[0];
73
+ }
74
+
75
+ args[0] = target_uri;
76
+ args[1] = response_uri;
77
+ args[2] = data;
78
+ rb_ary_push(messages, rb_class_new_instance(3, args, cRocketAMFMessage));
79
+ }
80
+
81
+ // Populate remoting object
82
+ rb_ivar_set(self, id_amf_version, INT2FIX(amf_ver));
83
+ rb_ivar_set(self, id_headers, headers);
84
+ rb_ivar_set(self, id_messages, messages);
85
+
86
+ return self;
87
+ }
88
+
89
+ /*
90
+ * call-seq:
91
+ * env.serialize(class_mapper=nil)
92
+ *
93
+ * Included into RocketAMF::Envelope, this method handles serializing an AMF
94
+ * request/response into a string
95
+ */
96
+ static VALUE env_serialize(int argc, VALUE *argv, VALUE self) {
97
+ static VALUE cClassMapper = 0;
98
+ if(cClassMapper == 0) cClassMapper = rb_const_get(mRocketAMF, rb_intern("ClassMapper"));
99
+
100
+ // Parse args
101
+ VALUE class_mapper;
102
+ rb_scan_args(argc, argv, "01", &class_mapper);
103
+ if(class_mapper == Qnil) class_mapper = rb_class_new_instance(0, NULL, cClassMapper);
104
+
105
+ // Get instance variables
106
+ long amf_ver = FIX2LONG(rb_ivar_get(self, id_amf_version));
107
+ VALUE headers = rb_funcall(rb_ivar_get(self, id_headers), rb_intern("values"), 0); // Get array of header values
108
+ VALUE messages = rb_ivar_get(self, id_messages);
109
+
110
+ // Create AMF0 serializer
111
+ VALUE args[1] = {class_mapper};
112
+ VALUE ser_rb = rb_class_new_instance(1, args, cSerializer);
113
+ AMF_SERIALIZER *ser;
114
+ Data_Get_Struct(ser_rb, AMF_SERIALIZER, ser);
115
+
116
+ // Write version
117
+ ser_write_uint16(ser, amf_ver);
118
+
119
+ // Write headers
120
+ long header_cnt = RARRAY_LEN(headers);
121
+ ser_write_uint16(ser, header_cnt);
122
+ int i;
123
+ char *str;
124
+ long str_len;
125
+ for(i = 0; i < header_cnt; i++) {
126
+ VALUE header = RARRAY_PTR(headers)[i];
127
+
128
+ // Write header name
129
+ ser_get_string(rb_funcall(header, rb_intern("name"), 0), Qtrue, &str, &str_len);
130
+ ser_write_uint16(ser, str_len);
131
+ rb_str_buf_cat(ser->stream, str, str_len);
132
+
133
+ // Write understand flag
134
+ ser_write_byte(ser, rb_funcall(header, rb_intern("must_understand"), 0) == Qtrue ? 1 : 0);
135
+
136
+ // Serialize data
137
+ ser_write_uint32(ser, -1); // length of data - -1 if you don't know
138
+ ser_serialize(ser_rb, INT2FIX(0), rb_funcall(header, id_data, 0));
139
+ }
140
+
141
+ // Write messages
142
+ long message_cnt = RARRAY_LEN(messages);
143
+ ser_write_uint16(ser, message_cnt);
144
+ for(i = 0; i < message_cnt; i++) {
145
+ VALUE message = RARRAY_PTR(messages)[i];
146
+
147
+ // Write target_uri
148
+ ser_get_string(rb_funcall(message, rb_intern("target_uri"), 0), Qtrue, &str, &str_len);
149
+ ser_write_uint16(ser, str_len);
150
+ rb_str_buf_cat(ser->stream, str, str_len);
151
+
152
+ // Write response_uri
153
+ ser_get_string(rb_funcall(message, rb_intern("response_uri"), 0), Qtrue, &str, &str_len);
154
+ ser_write_uint16(ser, str_len);
155
+ rb_str_buf_cat(ser->stream, str, str_len);
156
+
157
+ // Serialize data
158
+ ser_write_uint32(ser, -1); // length of data - -1 if you don't know
159
+ if(amf_ver == 3) {
160
+ ser_write_byte(ser, AMF0_AMF3_MARKER);
161
+ ser_serialize(ser_rb, INT2FIX(3), rb_funcall(message, id_data, 0));
162
+ } else {
163
+ ser_serialize(ser_rb, INT2FIX(0), rb_funcall(message, id_data, 0));
164
+ }
165
+ }
166
+
167
+ return ser->stream;
168
+ }
169
+
170
+
171
+ void Init_rocket_amf_remoting() {
172
+ VALUE mEnvelope = rb_define_module_under(mRocketAMFExt, "Envelope");
173
+ rb_define_method(mEnvelope, "populate_from_stream", env_populate_from_stream, -1);
174
+ rb_define_method(mEnvelope, "serialize", env_serialize, -1);
175
+
176
+ // Get refs to commonly used symbols and ids
177
+ id_amf_version = rb_intern("@amf_version");
178
+ id_headers = rb_intern("@headers");
179
+ id_messages = rb_intern("@messages");
180
+ id_data = rb_intern("data");
181
+ cRocketAMFHeader = rb_const_get(mRocketAMF, rb_intern("Header"));
182
+ cRocketAMFMessage = rb_const_get(mRocketAMF, rb_intern("Message"));
183
+ cRocketAMFAbstractMessage = rb_const_get(rb_const_get(mRocketAMF, rb_intern("Values")), rb_intern("AbstractMessage"));
184
+ }
@@ -0,0 +1,38 @@
1
+ #include <ruby.h>
2
+
3
+ VALUE mRocketAMF;
4
+ VALUE mRocketAMFExt;
5
+ VALUE cDeserializer;
6
+ VALUE cSerializer;
7
+ VALUE cStringIO;
8
+ VALUE cDate;
9
+ VALUE cDateTime;
10
+ VALUE sym_class_name;
11
+ VALUE sym_members;
12
+ VALUE sym_externalizable;
13
+ VALUE sym_dynamic;
14
+
15
+ void Init_rocket_amf_deserializer();
16
+ void Init_rocket_amf_serializer();
17
+ void Init_rocket_amf_fast_class_mapping();
18
+ void Init_rocket_amf_remoting();
19
+
20
+ void Init_rocketamf_ext() {
21
+ mRocketAMF = rb_define_module("RocketAMF");
22
+ mRocketAMFExt = rb_define_module_under(mRocketAMF, "Ext");
23
+
24
+ // Set up classes
25
+ Init_rocket_amf_deserializer();
26
+ Init_rocket_amf_serializer();
27
+ Init_rocket_amf_fast_class_mapping();
28
+ Init_rocket_amf_remoting();
29
+
30
+ // Get refs to commonly used symbols and ids
31
+ cStringIO = rb_const_get(rb_cObject, rb_intern("StringIO"));
32
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
33
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
34
+ sym_class_name = ID2SYM(rb_intern("class_name"));
35
+ sym_members = ID2SYM(rb_intern("members"));
36
+ sym_externalizable = ID2SYM(rb_intern("externalizable"));
37
+ sym_dynamic = ID2SYM(rb_intern("dynamic"));
38
+ }
@@ -0,0 +1,834 @@
1
+ #include "serializer.h"
2
+ #include "constants.h"
3
+ #include "utility.h"
4
+
5
+ extern VALUE mRocketAMF;
6
+ extern VALUE mRocketAMFExt;
7
+ extern VALUE cSerializer;
8
+ extern VALUE cStringIO;
9
+ extern VALUE cDate;
10
+ extern VALUE cDateTime;
11
+ extern VALUE sym_class_name;
12
+ extern VALUE sym_members;
13
+ extern VALUE sym_externalizable;
14
+ extern VALUE sym_dynamic;
15
+ VALUE cArrayCollection;
16
+ ID id_haskey;
17
+ ID id_encode_amf;
18
+ ID id_is_array_collection;
19
+ ID id_use_array_collection;
20
+ ID id_get_as_class_name;
21
+ ID id_props_for_serialization;
22
+ ID id_utc;
23
+ ID id_to_f;
24
+ ID id_is_integer;
25
+
26
+ static VALUE ser0_serialize(VALUE self, VALUE obj);
27
+ static VALUE ser3_serialize(VALUE self, VALUE obj);
28
+
29
+ void ser_write_byte(AMF_SERIALIZER *ser, char byte) {
30
+ char bytes[2] = {byte, '\0'};
31
+ rb_str_buf_cat(ser->stream, bytes, 1);
32
+ }
33
+
34
+ void ser_write_int(AMF_SERIALIZER *ser, int num) {
35
+ char tmp[4];
36
+ int tmp_len;
37
+
38
+ num &= 0x1fffffff;
39
+ if (num < 0x80) {
40
+ tmp_len = 1;
41
+ tmp[0] = num;
42
+ } else if (num < 0x4000) {
43
+ tmp_len = 2;
44
+ tmp[0] = (num >> 7 & 0x7f) | 0x80;
45
+ tmp[1] = num & 0x7f;
46
+ } else if (num < 0x200000) {
47
+ tmp_len = 3;
48
+ tmp[0] = (num >> 14 & 0x7f) | 0x80;
49
+ tmp[1] = (num >> 7 & 0x7f) | 0x80;
50
+ tmp[2] = num & 0x7f;
51
+ } else if (num < 0x40000000) {
52
+ tmp_len = 4;
53
+ tmp[0] = (num >> 22 & 0x7f) | 0x80;
54
+ tmp[1] = (num >> 15 & 0x7f) | 0x80;
55
+ tmp[2] = (num >> 8 & 0x7f) | 0x80;
56
+ tmp[3] = (num & 0xff);
57
+ } else {
58
+ rb_raise(rb_eRangeError, "int %d out of range", num);
59
+ }
60
+
61
+ rb_str_buf_cat(ser->stream, tmp, tmp_len);
62
+ }
63
+
64
+ void ser_write_uint16(AMF_SERIALIZER *ser, long num) {
65
+ if(num > 0xffff) rb_raise(rb_eRangeError, "int %ld out of range", num);
66
+ char tmp[2] = {(num >> 8) & 0xff, num & 0xff};
67
+ rb_str_buf_cat(ser->stream, tmp, 2);
68
+ }
69
+
70
+ void ser_write_uint32(AMF_SERIALIZER *ser, long num) {
71
+ if(num > 0xffffffff) rb_raise(rb_eRangeError, "int %ld out of range", num);
72
+ char tmp[4] = {(num >> 24) & 0xff, (num >> 16) & 0xff, (num >> 8) & 0xff, num & 0xff};
73
+ rb_str_buf_cat(ser->stream, tmp, 4);
74
+ }
75
+
76
+ void ser_write_double(AMF_SERIALIZER *ser, double num) {
77
+ union aligned {
78
+ double dval;
79
+ char cval[8];
80
+ } d;
81
+ const char *number = d.cval;
82
+ d.dval = num;
83
+
84
+ #ifdef WORDS_BIGENDIAN
85
+ rb_str_buf_cat(ser->stream, number, 8);
86
+ #else
87
+ char netnum[8] = {number[7],number[6],number[5],number[4],number[3],number[2],number[1],number[0]};
88
+ rb_str_buf_cat(ser->stream, netnum, 8);
89
+ #endif
90
+ }
91
+
92
+ void ser_get_string(VALUE obj, VALUE encode, char** str, long* len) {
93
+ int type = TYPE(obj);
94
+ if(type == T_STRING) {
95
+ #ifdef HAVE_RB_STR_ENCODE
96
+ if(encode == Qtrue) {
97
+ rb_encoding *enc = rb_enc_get(obj);
98
+ if (enc != rb_ascii8bit_encoding()) {
99
+ rb_encoding *utf8 = rb_utf8_encoding();
100
+ if (enc != utf8) obj = rb_str_encode(obj, rb_enc_from_encoding(utf8), 0, Qnil);
101
+ }
102
+ }
103
+ #endif
104
+ *str = RSTRING_PTR(obj);
105
+ *len = RSTRING_LEN(obj);
106
+ } else if(type == T_SYMBOL) {
107
+ *str = (char*)rb_id2name(SYM2ID(obj));
108
+ *len = strlen(*str);
109
+ } else if(obj == Qnil) {
110
+ *len = 0;
111
+ } else {
112
+ rb_raise(rb_eArgError, "Invalid type in ser_get_string: %d", type);
113
+ }
114
+ }
115
+
116
+ /*
117
+ * Write the given array in AMF0 notation
118
+ */
119
+ static void ser0_write_array(VALUE self, VALUE ary) {
120
+ AMF_SERIALIZER *ser;
121
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
122
+
123
+ // Cache it
124
+ st_add_direct(ser->obj_cache, ary, LONG2FIX(ser->obj_index));
125
+ ser->obj_index++;
126
+
127
+ // Write it out
128
+ long i, len = RARRAY_LEN(ary);
129
+ ser_write_byte(ser, AMF0_STRICT_ARRAY_MARKER);
130
+ ser_write_uint32(ser, len);
131
+ for(i = 0; i < len; i++) {
132
+ ser0_serialize(self, RARRAY_PTR(ary)[i]);
133
+ }
134
+ }
135
+
136
+ /*
137
+ * Supports writing strings and symbols. For hash keys, strings all have 16 bit
138
+ * lengths, so writing a type marker is unnecessary. In that case the third
139
+ * parameter should be set to Qfalse instead of Qtrue.
140
+ */
141
+ static void ser0_write_string(AMF_SERIALIZER *ser, VALUE obj, VALUE write_marker) {
142
+ // Extract char array and length from object
143
+ char* str;
144
+ long len;
145
+ ser_get_string(obj, Qtrue, &str, &len);
146
+
147
+ // Write string
148
+ if(len > 0xffff) {
149
+ if(write_marker == Qtrue) ser_write_byte(ser, AMF0_LONG_STRING_MARKER);
150
+ ser_write_uint32(ser, len);
151
+ } else {
152
+ if(write_marker == Qtrue) ser_write_byte(ser, AMF0_STRING_MARKER);
153
+ ser_write_uint16(ser, len);
154
+ }
155
+ rb_str_buf_cat(ser->stream, str, len);
156
+ }
157
+
158
+ /*
159
+ * Hash iterator for object properties that writes the key and then serializes
160
+ * the value
161
+ */
162
+ static int ser0_hash_iter(VALUE key, VALUE val, const VALUE args[1]) {
163
+ AMF_SERIALIZER *ser;
164
+ Data_Get_Struct(args[0], AMF_SERIALIZER, ser);
165
+
166
+ // Write key and value
167
+ 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
168
+ ser0_serialize(args[0], val);
169
+
170
+ return ST_CONTINUE;
171
+ }
172
+
173
+ /*
174
+ * Used for both hashes and objects. Takes the object and the props hash or Qnil,
175
+ * which forces a call to the class mapper for props for serialization. Prop
176
+ * sorting must be enabled by an explicit call to extconf.rb, so the tests will
177
+ * not pass typically on Ruby 1.8.
178
+ */
179
+ static void ser0_write_object(VALUE self, VALUE obj, VALUE props) {
180
+ AMF_SERIALIZER *ser;
181
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
182
+
183
+ // Cache it
184
+ st_add_direct(ser->obj_cache, obj, LONG2FIX(ser->obj_index));
185
+ ser->obj_index++;
186
+
187
+ // Make a request for props hash unless we already have it
188
+ if(props == Qnil) {
189
+ props = rb_funcall(ser->class_mapper, id_props_for_serialization, 1, obj);
190
+ }
191
+
192
+ // Write header
193
+ VALUE class_name = rb_funcall(ser->class_mapper, id_get_as_class_name, 1, obj);
194
+ if(class_name != Qnil) {
195
+ ser_write_byte(ser, AMF0_TYPED_OBJECT_MARKER);
196
+ ser0_write_string(ser, class_name, Qfalse);
197
+ } else {
198
+ ser_write_byte(ser, AMF0_OBJECT_MARKER);
199
+ }
200
+
201
+ // Write out data
202
+ VALUE args[1] = {self};
203
+ #ifdef SORT_PROPS
204
+ // Sort is required prior to Ruby 1.9 to pass all the tests, as Ruby 1.8 hashes don't store insert order
205
+ VALUE sorted_props = rb_funcall(props, rb_intern("sort"), 0);
206
+ long i, len = RARRAY_LEN(sorted_props);
207
+ for(i = 0; i < len; i++) {
208
+ VALUE pair = RARRAY_PTR(sorted_props)[i];
209
+ ser0_hash_iter(RARRAY_PTR(pair)[0], RARRAY_PTR(pair)[1], args);
210
+ }
211
+ #else
212
+ rb_hash_foreach(props, ser0_hash_iter, (st_data_t)args);
213
+ #endif
214
+
215
+ ser_write_uint16(ser, 0);
216
+ ser_write_byte(ser, AMF0_OBJECT_END_MARKER);
217
+ }
218
+
219
+ static void ser0_write_time(VALUE self, VALUE time) {
220
+ AMF_SERIALIZER *ser;
221
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
222
+
223
+ ser_write_byte(ser, AMF0_DATE_MARKER);
224
+
225
+ // Write time
226
+ time = rb_obj_dup(time);
227
+ rb_funcall(time, id_utc, 0);
228
+ double tmp_num = NUM2DBL(rb_funcall(time, id_to_f, 0)) * 1000;
229
+ ser_write_double(ser, tmp_num);
230
+ ser_write_uint16(ser, 0); // Time zone
231
+ }
232
+
233
+ static void ser0_write_date(VALUE self, VALUE date) {
234
+ AMF_SERIALIZER *ser;
235
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
236
+
237
+ ser_write_byte(ser, AMF0_DATE_MARKER);
238
+
239
+ // Write time
240
+ double tmp_num = rb_str_to_dbl(rb_funcall(date, rb_intern("strftime"), 1, rb_str_new2("%Q")), Qfalse);
241
+ ser_write_double(ser, tmp_num);
242
+ ser_write_uint16(ser, 0); // Time zone
243
+ }
244
+
245
+ /*
246
+ * Serializes the object to a string and returns that string
247
+ */
248
+ static VALUE ser0_serialize(VALUE self, VALUE obj) {
249
+ AMF_SERIALIZER *ser;
250
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
251
+
252
+ int type = TYPE(obj);
253
+ VALUE klass = Qnil;
254
+ if(type == T_OBJECT || type == T_DATA) {
255
+ klass = CLASS_OF(obj);
256
+ }
257
+
258
+ VALUE obj_index;
259
+ if(st_lookup(ser->obj_cache, obj, &obj_index)) {
260
+ ser_write_byte(ser, AMF0_REFERENCE_MARKER);
261
+ ser_write_uint16(ser, FIX2LONG(obj_index));
262
+ } else if(rb_respond_to(obj, id_encode_amf)) {
263
+ rb_funcall(obj, id_encode_amf, 1, self);
264
+ } else if(type == T_STRING || type == T_SYMBOL) {
265
+ ser0_write_string(ser, obj, Qtrue);
266
+ } else if(rb_obj_is_kind_of(obj, rb_cNumeric)) {
267
+ ser_write_byte(ser, AMF0_NUMBER_MARKER);
268
+ ser_write_double(ser, RFLOAT_VALUE(rb_Float(obj)));
269
+ } else if(type == T_NIL) {
270
+ ser_write_byte(ser, AMF0_NULL_MARKER);
271
+ } else if(type == T_TRUE || type == T_FALSE) {
272
+ ser_write_byte(ser, AMF0_BOOLEAN_MARKER);
273
+ ser_write_byte(ser, type == T_TRUE ? 1 : 0);
274
+ } else if(type == T_ARRAY) {
275
+ ser0_write_array(self, obj);
276
+ } else if(klass == rb_cTime) {
277
+ ser0_write_time(self, obj);
278
+ } else if(klass == cDate || klass == cDateTime) {
279
+ ser0_write_date(self, obj);
280
+ } else if(type == T_HASH || type == T_OBJECT) {
281
+ ser0_write_object(self, obj, Qnil);
282
+ }
283
+
284
+ return ser->stream;
285
+ }
286
+
287
+ /*
288
+ * Writes an AMF3 style string. Accepts strings, symbols, and nil, and handles
289
+ * all the necessary encoding and caching.
290
+ */
291
+ static void ser3_write_utf8vr(AMF_SERIALIZER *ser, VALUE obj) {
292
+ // Extract char array and length from object
293
+ char* str;
294
+ long len;
295
+ ser_get_string(obj, Qtrue, &str, &len);
296
+
297
+ // Write string
298
+ VALUE str_index;
299
+ if(len == 0) {
300
+ ser_write_byte(ser, AMF3_EMPTY_STRING);
301
+ } else if(st_lookup(ser->str_cache, (st_data_t)str, &str_index)) {
302
+ ser_write_int(ser, FIX2INT(str_index) << 1);
303
+ } else {
304
+ st_add_direct(ser->str_cache, (st_data_t)strdup(str), LONG2FIX(ser->str_index));
305
+ ser->str_index++;
306
+
307
+ ser_write_int(ser, ((int)len) << 1 | 1);
308
+ rb_str_buf_cat(ser->stream, str, len);
309
+ }
310
+ }
311
+
312
+ /*
313
+ * Writes Numeric conforming object using AMF3 notation
314
+ */
315
+ static void ser3_write_numeric(AMF_SERIALIZER *ser, VALUE num) {
316
+ // Is it an integer in range?
317
+ if(rb_funcall(num, id_is_integer, 0) == Qtrue) {
318
+ // It's an integer internally, so now we need to check if it's in range
319
+ VALUE int_obj = rb_Integer(num);
320
+ if(TYPE(int_obj) == T_FIXNUM) {
321
+ long long_val = FIX2LONG(int_obj);
322
+ if(long_val < MIN_INTEGER || long_val > MAX_INTEGER) {
323
+ // Outside range, but we have a value already, so just cast to double
324
+ ser_write_byte(ser, AMF3_DOUBLE_MARKER);
325
+ ser_write_double(ser, (double)long_val);
326
+ } else {
327
+ // Inside valid integer range
328
+ ser_write_byte(ser, AMF3_INTEGER_MARKER);
329
+ ser_write_int(ser, (int)long_val);
330
+ }
331
+ return;
332
+ }
333
+ }
334
+
335
+ // It's either not an integer or out of range, so write as a double
336
+ ser_write_byte(ser, AMF3_DOUBLE_MARKER);
337
+ ser_write_double(ser, RFLOAT_VALUE(rb_Float(num)));
338
+ }
339
+
340
+ /*
341
+ * Writes the given array using AMF3 notation
342
+ */
343
+ static void ser3_write_array(VALUE self, VALUE ary) {
344
+ AMF_SERIALIZER *ser;
345
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
346
+
347
+ // Is it an array collection?
348
+ VALUE is_ac = Qfalse;
349
+ if(rb_respond_to(ary, id_is_array_collection)) {
350
+ is_ac = rb_funcall(ary, id_is_array_collection, 0);
351
+ } else {
352
+ is_ac = rb_funcall(ser->class_mapper, id_use_array_collection, 0);
353
+ }
354
+
355
+ // Write type marker
356
+ ser_write_byte(ser, is_ac ? AMF3_OBJECT_MARKER : AMF3_ARRAY_MARKER);
357
+
358
+ // Write object ref, or cache it
359
+ VALUE obj_index;
360
+ if(st_lookup(ser->obj_cache, ary, &obj_index)) {
361
+ ser_write_int(ser, FIX2INT(obj_index) << 1);
362
+ return;
363
+ } else {
364
+ st_add_direct(ser->obj_cache, ary, LONG2FIX(ser->obj_index));
365
+ ser->obj_index++;
366
+ if(is_ac) ser->obj_index++; // The array collection source array
367
+ }
368
+
369
+ // Write out traits and array marker if it's an array collection
370
+ if(is_ac) {
371
+ VALUE trait_index;
372
+ char array_collection_name[34] = "flex.messaging.io.ArrayCollection";
373
+ if(st_lookup(ser->trait_cache, (st_data_t)array_collection_name, &trait_index)) {
374
+ ser_write_int(ser, FIX2INT(trait_index) << 2 | 0x01);
375
+ } else {
376
+ st_add_direct(ser->trait_cache, (st_data_t)strdup(array_collection_name), LONG2FIX(ser->trait_index));
377
+ ser->trait_index++;
378
+ ser_write_byte(ser, 0x07); // Trait header
379
+ ser3_write_utf8vr(ser, rb_str_new2(array_collection_name));
380
+ }
381
+ ser_write_byte(ser, AMF3_ARRAY_MARKER);
382
+ }
383
+
384
+ // Write header
385
+ int header = ((int)RARRAY_LEN(ary)) << 1 | 1;
386
+ ser_write_int(ser, header);
387
+ ser_write_byte(ser, AMF3_CLOSE_DYNAMIC_ARRAY);
388
+
389
+ // Write contents
390
+ long i, len = RARRAY_LEN(ary);
391
+ for(i = 0; i < len; i++) {
392
+ ser3_serialize(self, RARRAY_PTR(ary)[i]);
393
+ }
394
+ }
395
+
396
+ /*
397
+ * AMF3 property hash write iterator. Checks the args->extra hash, if given,
398
+ * and skips properties that are keys in that hash.
399
+ */
400
+ static int ser3_hash_iter(VALUE key, VALUE val, const VALUE args[2]) {
401
+ AMF_SERIALIZER *ser;
402
+ Data_Get_Struct(args[0], AMF_SERIALIZER, ser);
403
+
404
+ if(args[1] == Qnil || rb_funcall(args[1], id_haskey, 1, key) == Qfalse) {
405
+ // Write key and value
406
+ ser3_write_utf8vr(ser, key);
407
+ ser3_serialize(args[0], val);
408
+ }
409
+ return ST_CONTINUE;
410
+ }
411
+
412
+ /*
413
+ * Used for both hashes and objects. Takes the object and the props hash or Qnil,
414
+ * which forces a call to the class mapper for props for serialization. Prop
415
+ * sorting must be enabled by an explicit call to extconf.rb, so the tests will
416
+ * not pass typically on Ruby 1.8. If you need to have specific traits, you can
417
+ * also pass that in, or pass Qnil to use the default traits - dynamic with no
418
+ * defined members.
419
+ */
420
+ static void ser3_write_object(VALUE self, VALUE obj, VALUE props, VALUE traits) {
421
+ AMF_SERIALIZER *ser;
422
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
423
+ long i;
424
+
425
+ // Write type marker
426
+ ser_write_byte(ser, AMF3_OBJECT_MARKER);
427
+
428
+ // Write object ref, or cache it
429
+ VALUE obj_index;
430
+ if(st_lookup(ser->obj_cache, obj, &obj_index)) {
431
+ ser_write_int(ser, FIX2INT(obj_index) << 1);
432
+ return;
433
+ } else {
434
+ st_add_direct(ser->obj_cache, obj, LONG2FIX(ser->obj_index));
435
+ ser->obj_index++;
436
+ }
437
+
438
+ // Extract traits data, or use defaults
439
+ VALUE is_default = Qfalse;
440
+ VALUE class_name = Qnil;
441
+ VALUE members = Qnil;
442
+ long members_len = 0;
443
+ VALUE dynamic = Qtrue;
444
+ VALUE externalizable = Qfalse;
445
+ if(traits == Qnil) {
446
+ class_name = rb_funcall(ser->class_mapper, id_get_as_class_name, 1, obj);
447
+ if(class_name == Qnil) is_default = Qtrue;
448
+ } else {
449
+ class_name = rb_hash_aref(traits, sym_class_name);
450
+ members = rb_hash_aref(traits, sym_members);
451
+ if(members != Qnil) members_len = RARRAY_LEN(members);
452
+ dynamic = rb_hash_aref(traits, sym_dynamic);
453
+ externalizable = rb_hash_aref(traits, sym_externalizable);
454
+ }
455
+
456
+ // Handle trait caching
457
+ int did_ref = 0;
458
+ VALUE trait_index;
459
+ if(is_default == Qtrue || class_name != Qnil) {
460
+ const char *ref_class_name = is_default == Qtrue ? "__default__" : RSTRING_PTR(class_name);
461
+ if(st_lookup(ser->trait_cache, (st_data_t)ref_class_name, &trait_index)) {
462
+ ser_write_int(ser, FIX2INT(trait_index) << 2 | 0x01);
463
+ did_ref = 1;
464
+ } else {
465
+ st_add_direct(ser->trait_cache, (st_data_t)strdup(ref_class_name), LONG2FIX(ser->trait_index));
466
+ ser->trait_index++;
467
+ }
468
+ }
469
+
470
+ // Write traits outs if didn't write reference
471
+ if(!did_ref) {
472
+ // Write out trait header
473
+ int header = 0x03;
474
+ if(dynamic == Qtrue) header |= 0x02 << 2;
475
+ if(externalizable == Qtrue) header |= 0x01 << 2;
476
+ header |= ((int)members_len) << 4;
477
+ ser_write_int(ser, header);
478
+
479
+ // Write class name
480
+ ser3_write_utf8vr(ser, class_name);
481
+
482
+ // Write out members
483
+ for(i = 0; i < members_len; i++) {
484
+ ser3_write_utf8vr(ser, RARRAY_PTR(members)[i]);
485
+ }
486
+ }
487
+
488
+ // Raise exception if marked externalizable
489
+ if(externalizable == Qtrue) {
490
+ rb_funcall(obj, rb_intern("write_external"), 1, self);
491
+ return;
492
+ }
493
+
494
+ // Make a request for props hash unless we already have it
495
+ if(props == Qnil) {
496
+ props = rb_funcall(ser->class_mapper, id_props_for_serialization, 1, obj);
497
+ }
498
+
499
+ // Write sealed members
500
+ VALUE skipped_members = members_len ? rb_hash_new() : Qnil;
501
+ for(i = 0; i < members_len; i++) {
502
+ ser3_serialize(self, rb_hash_aref(props, RARRAY_PTR(members)[i]));
503
+ rb_hash_aset(skipped_members, RARRAY_PTR(members)[i], Qtrue);
504
+ }
505
+
506
+ // Write dynamic properties
507
+ if(dynamic == Qtrue) {
508
+ VALUE args[2] = {self, skipped_members};
509
+ #ifdef SORT_PROPS
510
+ // Sort is required prior to Ruby 1.9 to pass all the tests, as Ruby 1.8 hashes don't store insert order
511
+ VALUE sorted_props = rb_funcall(props, rb_intern("sort"), 0);
512
+ for(i = 0; i < RARRAY_LEN(sorted_props); i++) {
513
+ VALUE pair = RARRAY_PTR(sorted_props)[i];
514
+ ser3_hash_iter(RARRAY_PTR(pair)[0], RARRAY_PTR(pair)[1], args);
515
+ }
516
+ #else
517
+ rb_hash_foreach(props, ser3_hash_iter, (st_data_t)args);
518
+ #endif
519
+
520
+ ser_write_byte(ser, AMF3_CLOSE_DYNAMIC_OBJECT);
521
+ }
522
+ }
523
+
524
+ static void ser3_write_time(VALUE self, VALUE time_obj) {
525
+ AMF_SERIALIZER *ser;
526
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
527
+
528
+ ser_write_byte(ser, AMF3_DATE_MARKER);
529
+
530
+ // Write object ref, or cache it
531
+ VALUE obj_index;
532
+ if(st_lookup(ser->obj_cache, time_obj, &obj_index)) {
533
+ ser_write_int(ser, FIX2INT(obj_index) << 1);
534
+ return;
535
+ } else {
536
+ st_add_direct(ser->obj_cache, time_obj, LONG2FIX(ser->obj_index));
537
+ ser->obj_index++;
538
+ }
539
+
540
+ // Write time
541
+ ser_write_byte(ser, AMF3_NULL_MARKER); // Ref header
542
+ time_obj = rb_obj_dup(time_obj);
543
+ rb_funcall(time_obj, id_utc, 0);
544
+ double tmp_num = NUM2DBL(rb_funcall(time_obj, id_to_f, 0)) * 1000;
545
+ ser_write_double(ser, tmp_num);
546
+ }
547
+
548
+ static void ser3_write_date(VALUE self, VALUE date) {
549
+ AMF_SERIALIZER *ser;
550
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
551
+
552
+ ser_write_byte(ser, AMF3_DATE_MARKER);
553
+
554
+ // Write object ref, or cache it
555
+ VALUE obj_index;
556
+ if(st_lookup(ser->obj_cache, date, &obj_index)) {
557
+ ser_write_int(ser, FIX2INT(obj_index) << 1);
558
+ return;
559
+ } else {
560
+ st_add_direct(ser->obj_cache, date, LONG2FIX(ser->obj_index));
561
+ ser->obj_index++;
562
+ }
563
+
564
+ // Write time
565
+ ser_write_byte(ser, AMF3_NULL_MARKER); // Ref header
566
+ double tmp_num = rb_str_to_dbl(rb_funcall(date, rb_intern("strftime"), 1, rb_str_new2("%Q")), Qfalse);
567
+ ser_write_double(ser, tmp_num);
568
+ }
569
+
570
+ static void ser3_write_byte_array(VALUE self, VALUE ba) {
571
+ AMF_SERIALIZER *ser;
572
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
573
+
574
+ ser_write_byte(ser, AMF3_BYTE_ARRAY_MARKER);
575
+
576
+ // Write object ref, or cache it
577
+ VALUE obj_index;
578
+ if(st_lookup(ser->obj_cache, ba, &obj_index)) {
579
+ ser_write_int(ser, FIX2INT(obj_index) << 1);
580
+ return;
581
+ } else {
582
+ st_add_direct(ser->obj_cache, ba, LONG2FIX(ser->obj_index));
583
+ ser->obj_index++;
584
+ }
585
+
586
+ // Write byte array
587
+ VALUE str = rb_funcall(ba, rb_intern("string"), 0);
588
+ int len = (int)(RSTRING_LEN(str) << 1); // Explicitly cast to int to avoid compiler warning
589
+ ser_write_int(ser, len | 1);
590
+ rb_str_buf_cat(ser->stream, RSTRING_PTR(str), RSTRING_LEN(str));
591
+ }
592
+
593
+ /*
594
+ * Serializes the object to a string and returns that string
595
+ */
596
+ static VALUE ser3_serialize(VALUE self, VALUE obj) {
597
+ AMF_SERIALIZER *ser;
598
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
599
+
600
+ int type = TYPE(obj);
601
+ VALUE klass = Qnil;
602
+ if(type == T_OBJECT || type == T_DATA || type == T_ARRAY) {
603
+ klass = CLASS_OF(obj);
604
+ }
605
+
606
+ if(rb_respond_to(obj, id_encode_amf)) {
607
+ rb_funcall(obj, id_encode_amf, 1, self);
608
+ } else if(type == T_STRING || type == T_SYMBOL) {
609
+ ser_write_byte(ser, AMF3_STRING_MARKER);
610
+ ser3_write_utf8vr(ser, obj);
611
+ } else if(rb_obj_is_kind_of(obj, rb_cNumeric)) {
612
+ ser3_write_numeric(ser, obj);
613
+ } else if(type == T_NIL) {
614
+ ser_write_byte(ser, AMF3_NULL_MARKER);
615
+ } else if(type == T_TRUE) {
616
+ ser_write_byte(ser, AMF3_TRUE_MARKER);
617
+ } else if(type == T_FALSE) {
618
+ ser_write_byte(ser, AMF3_FALSE_MARKER);
619
+ } else if(type == T_ARRAY) {
620
+ ser3_write_array(self, obj);
621
+ } else if(type == T_HASH) {
622
+ ser3_write_object(self, obj, Qnil, Qnil);
623
+ } else if(klass == rb_cTime) {
624
+ ser3_write_time(self, obj);
625
+ } else if(klass == cDate || klass == cDateTime) {
626
+ ser3_write_date(self, obj);
627
+ } else if(klass == cStringIO) {
628
+ ser3_write_byte_array(self, obj);
629
+ } else if(type == T_OBJECT) {
630
+ ser3_write_object(self, obj, Qnil, Qnil);
631
+ }
632
+
633
+ return ser->stream;
634
+ }
635
+
636
+ /*
637
+ * Mark ruby objects for GC
638
+ */
639
+ static void ser_mark(AMF_SERIALIZER *ser) {
640
+ if(!ser) return;
641
+ rb_gc_mark(ser->class_mapper);
642
+ rb_gc_mark(ser->stream);
643
+ }
644
+
645
+ /*
646
+ * Free cache tables, stream and the struct itself
647
+ */
648
+ int ser_free_strtable_key(st_data_t key, st_data_t value, st_data_t ignored)
649
+ {
650
+ xfree((void *)key);
651
+
652
+ return ST_DELETE;
653
+ }
654
+ inline void ser_free_cache(AMF_SERIALIZER *ser) {
655
+ if(ser->str_cache) {
656
+ st_foreach(ser->str_cache, ser_free_strtable_key, 0);
657
+ st_free_table(ser->str_cache);
658
+ ser->str_cache = NULL;
659
+ }
660
+ if(ser->trait_cache) {
661
+ st_foreach(ser->trait_cache, ser_free_strtable_key, 0);
662
+ st_free_table(ser->trait_cache);
663
+ ser->trait_cache = NULL;
664
+ }
665
+ if(ser->obj_cache) {
666
+ st_free_table(ser->obj_cache);
667
+ ser->obj_cache = NULL;
668
+ }
669
+ }
670
+ static void ser_free(AMF_SERIALIZER *ser) {
671
+ ser_free_cache(ser);
672
+ xfree(ser);
673
+ }
674
+
675
+ /*
676
+ * Create new struct and wrap with class
677
+ */
678
+ static VALUE ser_alloc(VALUE klass) {
679
+ // Allocate struct
680
+ AMF_SERIALIZER *ser = ALLOC(AMF_SERIALIZER);
681
+ memset(ser, 0, sizeof(AMF_SERIALIZER));
682
+ return Data_Wrap_Struct(klass, ser_mark, ser_free, ser);
683
+ }
684
+
685
+ /*
686
+ * Initializer
687
+ */
688
+ static VALUE ser_initialize(VALUE self, VALUE class_mapper) {
689
+ AMF_SERIALIZER *ser;
690
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
691
+
692
+ ser->class_mapper = class_mapper;
693
+ ser->depth = 0;
694
+ ser->stream = rb_str_buf_new(0);
695
+
696
+ return self;
697
+ }
698
+
699
+ /*
700
+ * call-seq:
701
+ * ser.version => int
702
+ *
703
+ * Returns the serializer version number, so that a custom encode_amf method
704
+ * knows which version to encode for
705
+ */
706
+ static VALUE ser_version(VALUE self) {
707
+ AMF_SERIALIZER *ser;
708
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
709
+ return INT2FIX(ser->version);
710
+ }
711
+
712
+ /*
713
+ * call-seq:
714
+ * ser.stream => string
715
+ *
716
+ * Returns the string that the serializer is writing to
717
+ */
718
+ static VALUE ser_stream(VALUE self) {
719
+ AMF_SERIALIZER *ser;
720
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
721
+ return ser->stream;
722
+ }
723
+
724
+ /*
725
+ * call-seq:
726
+ * ser.serialize(amf_ver, obj) => string
727
+ *
728
+ * Serialize the given object to the current stream and returns the stream
729
+ */
730
+ VALUE ser_serialize(VALUE self, VALUE ver, VALUE obj) {
731
+ AMF_SERIALIZER *ser;
732
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
733
+
734
+ // Process version
735
+ int int_ver = FIX2INT(ver);
736
+ if(int_ver != 0 && int_ver != 3) rb_raise(rb_eArgError, "unsupported version %d", int_ver);
737
+ ser->version = int_ver;
738
+
739
+ // Initialize caches
740
+ if(ser->depth == 0) {
741
+ ser->obj_cache = st_init_numtable();
742
+ ser->obj_index = 0;
743
+ if(ser->version == 3) {
744
+ ser->str_cache = st_init_strtable();
745
+ ser->str_index = 0;
746
+ ser->trait_cache = st_init_strtable();
747
+ ser->trait_index = 0;
748
+ }
749
+ }
750
+ ser->depth++;
751
+
752
+ // Perform serialization
753
+ if(ser->version == 0) {
754
+ ser0_serialize(self, obj);
755
+ } else {
756
+ ser3_serialize(self, obj);
757
+ }
758
+
759
+ // Clean up
760
+ ser->depth--;
761
+ if(ser->depth == 0) ser_free_cache(ser);
762
+
763
+ return ser->stream;
764
+ }
765
+
766
+ /*
767
+ * call-seq:
768
+ * ser.write_array(ary) => ser
769
+ *
770
+ * Serializes the given array to the serializer stream
771
+ */
772
+ static VALUE ser_write_array(VALUE self, VALUE ary) {
773
+ AMF_SERIALIZER *ser;
774
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
775
+ if(ser->version == 0) {
776
+ ser0_write_array(self, ary);
777
+ } else {
778
+ ser3_write_array(self, ary);
779
+ }
780
+ return self;
781
+ }
782
+
783
+ /*
784
+ * call-seq:
785
+ * ser.write_object(obj, props=nil) => ser
786
+ * ser.write_object(obj, props=nil, traits=nil) => ser
787
+ *
788
+ * Serializes the given object or hash to the serializer stream using
789
+ * the proper serializer version. If given a props hash, uses that
790
+ * instead of using the class mapper to calculate it. If given a traits
791
+ * hash for AMF3, uses that instead of the default dynamic traits with
792
+ * the mapped class name.
793
+ */
794
+ static VALUE ser_write_object(int argc, VALUE *argv, VALUE self) {
795
+ AMF_SERIALIZER *ser;
796
+ Data_Get_Struct(self, AMF_SERIALIZER, ser);
797
+
798
+ // Check args and call implementation
799
+ VALUE obj;
800
+ VALUE props = Qnil;
801
+ VALUE traits = Qnil;
802
+ if(ser->version == 0) {
803
+ rb_scan_args(argc, argv, "11", &obj, &props);
804
+ ser0_write_object(self, obj, props);
805
+ } else {
806
+ rb_scan_args(argc, argv, "12", &obj, &props, &traits);
807
+ ser3_write_object(self, obj, props, traits);
808
+ }
809
+
810
+ return self;
811
+ }
812
+
813
+ void Init_rocket_amf_serializer() {
814
+ // Define Serializer
815
+ cSerializer = rb_define_class_under(mRocketAMFExt, "Serializer", rb_cObject);
816
+ rb_define_alloc_func(cSerializer, ser_alloc);
817
+ rb_define_method(cSerializer, "initialize", ser_initialize, 1);
818
+ rb_define_method(cSerializer, "version", ser_version, 0);
819
+ rb_define_method(cSerializer, "stream", ser_stream, 0);
820
+ rb_define_method(cSerializer, "serialize", ser_serialize, 2);
821
+ rb_define_method(cSerializer, "write_array", ser_write_array, 1);
822
+ rb_define_method(cSerializer, "write_object", ser_write_object, -1);
823
+
824
+ // Get refs to commonly used symbols and ids
825
+ id_haskey = rb_intern("has_key?");
826
+ id_encode_amf = rb_intern("encode_amf");
827
+ id_is_array_collection = rb_intern("is_array_collection?");
828
+ id_use_array_collection = rb_intern("use_array_collection");
829
+ id_get_as_class_name = rb_intern("get_as_class_name");
830
+ id_props_for_serialization = rb_intern("props_for_serialization");
831
+ id_utc = rb_intern("utc");
832
+ id_to_f = rb_intern("to_f");
833
+ id_is_integer = rb_intern("integer?");
834
+ }