mrpin-amf 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/README.rdoc +52 -0
  4. data/Rakefile +59 -0
  5. data/benchmark.rb +86 -0
  6. data/doc/amf3-speification.pdf +0 -0
  7. data/ext/rocketamf_ext/class_mapping.c +483 -0
  8. data/ext/rocketamf_ext/constants.h +52 -0
  9. data/ext/rocketamf_ext/deserializer.c +776 -0
  10. data/ext/rocketamf_ext/deserializer.h +28 -0
  11. data/ext/rocketamf_ext/extconf.rb +18 -0
  12. data/ext/rocketamf_ext/remoting.c +184 -0
  13. data/ext/rocketamf_ext/rocketamf_ext.c +38 -0
  14. data/ext/rocketamf_ext/serializer.c +834 -0
  15. data/ext/rocketamf_ext/serializer.h +29 -0
  16. data/ext/rocketamf_ext/utility.h +4 -0
  17. data/lib/amf/common/hash_with_type.rb +20 -0
  18. data/lib/amf/ext.rb +22 -0
  19. data/lib/amf/pure/amf_constants.rb +42 -0
  20. data/lib/amf/pure/deserializer.rb +354 -0
  21. data/lib/amf/pure/errors/all_files.rb +2 -0
  22. data/lib/amf/pure/errors/amf_error.rb +5 -0
  23. data/lib/amf/pure/errors/amf_error_incomplete.rb +5 -0
  24. data/lib/amf/pure/helpers/all_files.rb +8 -0
  25. data/lib/amf/pure/helpers/cache_objects.rb +18 -0
  26. data/lib/amf/pure/helpers/cache_strings.rb +14 -0
  27. data/lib/amf/pure/helpers/io_helper_base.rb +19 -0
  28. data/lib/amf/pure/helpers/io_helper_read.rb +67 -0
  29. data/lib/amf/pure/helpers/io_helper_write.rb +49 -0
  30. data/lib/amf/pure/mapping/class_mapper.rb +159 -0
  31. data/lib/amf/pure/mapping/mapping_set.rb +49 -0
  32. data/lib/amf/pure/serializer.rb +318 -0
  33. data/lib/amf/pure.rb +16 -0
  34. data/lib/amf.rb +140 -0
  35. data/mrpin-amf.gemspec +24 -0
  36. data/spec/fixtures/objects/complex/amf3-associative-array.bin +1 -0
  37. data/spec/fixtures/objects/complex/amf3-byte-array.bin +0 -0
  38. data/spec/fixtures/objects/complex/amf3-date.bin +0 -0
  39. data/spec/fixtures/objects/complex/amf3-dictionary.bin +0 -0
  40. data/spec/fixtures/objects/complex/amf3-dynamic-object.bin +2 -0
  41. data/spec/fixtures/objects/complex/amf3-empty-array.bin +1 -0
  42. data/spec/fixtures/objects/complex/amf3-empty-dictionary.bin +0 -0
  43. data/spec/fixtures/objects/complex/amf3-hash.bin +2 -0
  44. data/spec/fixtures/objects/complex/amf3-mixed-array.bin +10 -0
  45. data/spec/fixtures/objects/complex/amf3-primitive-array.bin +1 -0
  46. data/spec/fixtures/objects/complex/amf3-typed-object.bin +2 -0
  47. data/spec/fixtures/objects/encoding/amf3-complex-encoded-string-array.bin +1 -0
  48. data/spec/fixtures/objects/encoding/amf3-encoded-string-ref.bin +0 -0
  49. data/spec/fixtures/objects/references/amf3-array-ref.bin +1 -0
  50. data/spec/fixtures/objects/references/amf3-byte-array-ref.bin +1 -0
  51. data/spec/fixtures/objects/references/amf3-date-ref.bin +0 -0
  52. data/spec/fixtures/objects/references/amf3-empty-array-ref.bin +1 -0
  53. data/spec/fixtures/objects/references/amf3-empty-string-ref.bin +1 -0
  54. data/spec/fixtures/objects/references/amf3-graph-member.bin +0 -0
  55. data/spec/fixtures/objects/references/amf3-object-ref.bin +0 -0
  56. data/spec/fixtures/objects/references/amf3-string-ref.bin +0 -0
  57. data/spec/fixtures/objects/references/amf3-trait-ref.bin +3 -0
  58. data/spec/fixtures/objects/simple/amf3-0.bin +0 -0
  59. data/spec/fixtures/objects/simple/amf3-bigNum.bin +0 -0
  60. data/spec/fixtures/objects/simple/amf3-false.bin +1 -0
  61. data/spec/fixtures/objects/simple/amf3-float.bin +0 -0
  62. data/spec/fixtures/objects/simple/amf3-large-max.bin +0 -0
  63. data/spec/fixtures/objects/simple/amf3-large-min.bin +0 -0
  64. data/spec/fixtures/objects/simple/amf3-max.bin +1 -0
  65. data/spec/fixtures/objects/simple/amf3-min.bin +0 -0
  66. data/spec/fixtures/objects/simple/amf3-null.bin +1 -0
  67. data/spec/fixtures/objects/simple/amf3-string.bin +1 -0
  68. data/spec/fixtures/objects/simple/amf3-symbol.bin +1 -0
  69. data/spec/fixtures/objects/simple/amf3-true.bin +1 -0
  70. data/spec/helpers/class_mapping_test.rb +4 -0
  71. data/spec/helpers/class_mapping_test2.rb +3 -0
  72. data/spec/helpers/fixtures.rb +34 -0
  73. data/spec/helpers/other_class.rb +4 -0
  74. data/spec/helpers/ruby_class.rb +4 -0
  75. data/spec/helpers/test_ruby_class.rb +4 -0
  76. data/spec/spec-class_mapping.rb +98 -0
  77. data/spec/spec_deserializer.rb +239 -0
  78. data/spec/spec_fast_class_mapping.rb +147 -0
  79. data/spec/spec_helper.rb +8 -0
  80. data/spec/spec_serializer.rb +267 -0
  81. metadata +146 -0
@@ -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
+ static 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
+ }