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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/README.rdoc +52 -0
- data/Rakefile +59 -0
- data/benchmark.rb +86 -0
- data/doc/amf3-speification.pdf +0 -0
- data/ext/rocketamf_ext/class_mapping.c +483 -0
- data/ext/rocketamf_ext/constants.h +52 -0
- data/ext/rocketamf_ext/deserializer.c +776 -0
- data/ext/rocketamf_ext/deserializer.h +28 -0
- data/ext/rocketamf_ext/extconf.rb +18 -0
- data/ext/rocketamf_ext/remoting.c +184 -0
- data/ext/rocketamf_ext/rocketamf_ext.c +38 -0
- data/ext/rocketamf_ext/serializer.c +834 -0
- data/ext/rocketamf_ext/serializer.h +29 -0
- data/ext/rocketamf_ext/utility.h +4 -0
- data/lib/amf/common/hash_with_type.rb +20 -0
- data/lib/amf/ext.rb +22 -0
- data/lib/amf/pure/amf_constants.rb +42 -0
- data/lib/amf/pure/deserializer.rb +354 -0
- data/lib/amf/pure/errors/all_files.rb +2 -0
- data/lib/amf/pure/errors/amf_error.rb +5 -0
- data/lib/amf/pure/errors/amf_error_incomplete.rb +5 -0
- data/lib/amf/pure/helpers/all_files.rb +8 -0
- data/lib/amf/pure/helpers/cache_objects.rb +18 -0
- data/lib/amf/pure/helpers/cache_strings.rb +14 -0
- data/lib/amf/pure/helpers/io_helper_base.rb +19 -0
- data/lib/amf/pure/helpers/io_helper_read.rb +67 -0
- data/lib/amf/pure/helpers/io_helper_write.rb +49 -0
- data/lib/amf/pure/mapping/class_mapper.rb +159 -0
- data/lib/amf/pure/mapping/mapping_set.rb +49 -0
- data/lib/amf/pure/serializer.rb +318 -0
- data/lib/amf/pure.rb +16 -0
- data/lib/amf.rb +140 -0
- data/mrpin-amf.gemspec +24 -0
- data/spec/fixtures/objects/complex/amf3-associative-array.bin +1 -0
- data/spec/fixtures/objects/complex/amf3-byte-array.bin +0 -0
- data/spec/fixtures/objects/complex/amf3-date.bin +0 -0
- data/spec/fixtures/objects/complex/amf3-dictionary.bin +0 -0
- data/spec/fixtures/objects/complex/amf3-dynamic-object.bin +2 -0
- data/spec/fixtures/objects/complex/amf3-empty-array.bin +1 -0
- data/spec/fixtures/objects/complex/amf3-empty-dictionary.bin +0 -0
- data/spec/fixtures/objects/complex/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/complex/amf3-mixed-array.bin +10 -0
- data/spec/fixtures/objects/complex/amf3-primitive-array.bin +1 -0
- data/spec/fixtures/objects/complex/amf3-typed-object.bin +2 -0
- data/spec/fixtures/objects/encoding/amf3-complex-encoded-string-array.bin +1 -0
- data/spec/fixtures/objects/encoding/amf3-encoded-string-ref.bin +0 -0
- data/spec/fixtures/objects/references/amf3-array-ref.bin +1 -0
- data/spec/fixtures/objects/references/amf3-byte-array-ref.bin +1 -0
- data/spec/fixtures/objects/references/amf3-date-ref.bin +0 -0
- data/spec/fixtures/objects/references/amf3-empty-array-ref.bin +1 -0
- data/spec/fixtures/objects/references/amf3-empty-string-ref.bin +1 -0
- data/spec/fixtures/objects/references/amf3-graph-member.bin +0 -0
- data/spec/fixtures/objects/references/amf3-object-ref.bin +0 -0
- data/spec/fixtures/objects/references/amf3-string-ref.bin +0 -0
- data/spec/fixtures/objects/references/amf3-trait-ref.bin +3 -0
- data/spec/fixtures/objects/simple/amf3-0.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-false.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-float.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-large-max.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-large-min.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-max.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-min.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-null.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-string.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-true.bin +1 -0
- data/spec/helpers/class_mapping_test.rb +4 -0
- data/spec/helpers/class_mapping_test2.rb +3 -0
- data/spec/helpers/fixtures.rb +34 -0
- data/spec/helpers/other_class.rb +4 -0
- data/spec/helpers/ruby_class.rb +4 -0
- data/spec/helpers/test_ruby_class.rb +4 -0
- data/spec/spec-class_mapping.rb +98 -0
- data/spec/spec_deserializer.rb +239 -0
- data/spec/spec_fast_class_mapping.rb +147 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/spec_serializer.rb +267 -0
- metadata +146 -0
@@ -0,0 +1,776 @@
|
|
1
|
+
#include "deserializer.h"
|
2
|
+
#include "constants.h"
|
3
|
+
|
4
|
+
#define DES_BOUNDS_CHECK(des, i) if(des->pos + (i) > des->size || des->pos + (i) < des->pos) rb_raise(rb_eRangeError, "reading %lu bytes is beyond end of source: %ld (pos), %ld (size)", (unsigned long)(i), des->pos, des->size);
|
5
|
+
|
6
|
+
extern VALUE mRocketAMF;
|
7
|
+
extern VALUE mRocketAMFExt;
|
8
|
+
extern VALUE cDeserializer;
|
9
|
+
extern VALUE cStringIO;
|
10
|
+
extern VALUE sym_class_name;
|
11
|
+
extern VALUE sym_members;
|
12
|
+
extern VALUE sym_externalizable;
|
13
|
+
extern VALUE sym_dynamic;
|
14
|
+
ID id_get_ruby_obj;
|
15
|
+
ID id_populate_ruby_obj;
|
16
|
+
|
17
|
+
static VALUE des0_deserialize(VALUE self, char type);
|
18
|
+
static VALUE des3_deserialize(VALUE self);
|
19
|
+
|
20
|
+
char des_read_byte(AMF_DESERIALIZER *des) {
|
21
|
+
DES_BOUNDS_CHECK(des, 1);
|
22
|
+
des->pos++;
|
23
|
+
return des->stream[des->pos-1];
|
24
|
+
}
|
25
|
+
|
26
|
+
char des_read_ahead_byte(AMF_DESERIALIZER *des) {
|
27
|
+
DES_BOUNDS_CHECK(des, 1);
|
28
|
+
return des->stream[des->pos];
|
29
|
+
}
|
30
|
+
|
31
|
+
int des_read_uint16(AMF_DESERIALIZER *des) {
|
32
|
+
DES_BOUNDS_CHECK(des, 2);
|
33
|
+
const unsigned char *str = (unsigned char*)(des->stream) + des->pos;
|
34
|
+
des->pos += 2;
|
35
|
+
return ((str[0] << 8) | str[1]);
|
36
|
+
}
|
37
|
+
|
38
|
+
unsigned int des_read_uint32(AMF_DESERIALIZER *des) {
|
39
|
+
DES_BOUNDS_CHECK(des, 4);
|
40
|
+
const unsigned char *str = (unsigned char*)(des->stream) + des->pos;
|
41
|
+
des->pos += 4;
|
42
|
+
return ((str[0] << 24) | (str[1] << 16) | (str[2] << 8) | str[3]);
|
43
|
+
}
|
44
|
+
|
45
|
+
/*
|
46
|
+
* Read a network double
|
47
|
+
*/
|
48
|
+
double des_read_double(AMF_DESERIALIZER *des) {
|
49
|
+
DES_BOUNDS_CHECK(des, 8);
|
50
|
+
union aligned {
|
51
|
+
double dval;
|
52
|
+
char cval[8];
|
53
|
+
} d;
|
54
|
+
const char *str = des->stream + des->pos;
|
55
|
+
des->pos +=8;
|
56
|
+
|
57
|
+
#ifdef WORDS_BIGENDIAN
|
58
|
+
memcpy(d.cval, str, 8);
|
59
|
+
#else
|
60
|
+
d.cval[0] = str[7];
|
61
|
+
d.cval[1] = str[6];
|
62
|
+
d.cval[2] = str[5];
|
63
|
+
d.cval[3] = str[4];
|
64
|
+
d.cval[4] = str[3];
|
65
|
+
d.cval[5] = str[2];
|
66
|
+
d.cval[6] = str[1];
|
67
|
+
d.cval[7] = str[0];
|
68
|
+
#endif
|
69
|
+
return d.dval;
|
70
|
+
}
|
71
|
+
|
72
|
+
/*
|
73
|
+
* Read an AMF3 style integer
|
74
|
+
*/
|
75
|
+
int des_read_int(AMF_DESERIALIZER *des) {
|
76
|
+
int result = 0, byte_cnt = 0;
|
77
|
+
DES_BOUNDS_CHECK(des, 1);
|
78
|
+
unsigned char byte = des->stream[des->pos++];
|
79
|
+
|
80
|
+
while(byte & 0x80 && byte_cnt < 3) {
|
81
|
+
result <<= 7;
|
82
|
+
result |= byte & 0x7f;
|
83
|
+
DES_BOUNDS_CHECK(des, 1);
|
84
|
+
byte = des->stream[des->pos++];
|
85
|
+
byte_cnt++;
|
86
|
+
}
|
87
|
+
|
88
|
+
if (byte_cnt < 3) {
|
89
|
+
result <<= 7;
|
90
|
+
result |= byte & 0x7F;
|
91
|
+
} else {
|
92
|
+
result <<= 8;
|
93
|
+
result |= byte & 0xff;
|
94
|
+
}
|
95
|
+
|
96
|
+
if (result & 0x10000000) {
|
97
|
+
result -= 0x20000000;
|
98
|
+
}
|
99
|
+
|
100
|
+
return result;
|
101
|
+
}
|
102
|
+
|
103
|
+
/*
|
104
|
+
* Read a string and then force the encoding to UTF 8 if running ruby 1.9
|
105
|
+
*/
|
106
|
+
VALUE des_read_string(AMF_DESERIALIZER *des, unsigned int len) {
|
107
|
+
DES_BOUNDS_CHECK(des, len);
|
108
|
+
VALUE str = rb_str_new(des->stream + des->pos, len);
|
109
|
+
#ifdef HAVE_RB_STR_ENCODE
|
110
|
+
rb_encoding *utf8 = rb_utf8_encoding();
|
111
|
+
rb_enc_associate(str, utf8);
|
112
|
+
ENC_CODERANGE_CLEAR(str);
|
113
|
+
#endif
|
114
|
+
des->pos += len;
|
115
|
+
return str;
|
116
|
+
}
|
117
|
+
|
118
|
+
/*
|
119
|
+
* Set the source of the amf reader to a StringIO object, creating a new one to
|
120
|
+
* wrap the source if it's only a string
|
121
|
+
*/
|
122
|
+
void des_set_src(AMF_DESERIALIZER *des, VALUE src) {
|
123
|
+
VALUE klass = CLASS_OF(src);
|
124
|
+
if(klass == cStringIO) {
|
125
|
+
VALUE str = rb_funcall(src, rb_intern("string"), 0);
|
126
|
+
des->src = src;
|
127
|
+
des->stream = RSTRING_PTR(str);
|
128
|
+
des->pos = NUM2LONG(rb_funcall(src, rb_intern("pos"), 0));
|
129
|
+
des->size = RSTRING_LEN(str);
|
130
|
+
} else if(klass == rb_cString) {
|
131
|
+
VALUE args[1] = {src};
|
132
|
+
des->src = rb_class_new_instance(1, args, cStringIO);
|
133
|
+
des->stream = RSTRING_PTR(src);
|
134
|
+
des->pos = 0;
|
135
|
+
des->size = RSTRING_LEN(src);
|
136
|
+
} else {
|
137
|
+
rb_raise(rb_eArgError, "Invalid source type to deserialize from");
|
138
|
+
}
|
139
|
+
|
140
|
+
if(des->pos >= des->size) rb_raise(rb_eRangeError, "already at the end of the source");
|
141
|
+
}
|
142
|
+
|
143
|
+
/*
|
144
|
+
* Create AMF3 deserializer and copy source data over to it, before calling
|
145
|
+
* AMF3 internal deserialize function
|
146
|
+
*/
|
147
|
+
static VALUE des0_read_amf3(VALUE self) {
|
148
|
+
AMF_DESERIALIZER *des;
|
149
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
150
|
+
des->version = 3;
|
151
|
+
des->str_cache = rb_ary_new();
|
152
|
+
des->trait_cache = rb_ary_new();
|
153
|
+
return des3_deserialize(self);
|
154
|
+
}
|
155
|
+
|
156
|
+
/*
|
157
|
+
* Reads an AMF0 hash
|
158
|
+
*/
|
159
|
+
static void des0_read_props(VALUE self, VALUE hash) {
|
160
|
+
AMF_DESERIALIZER *des;
|
161
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
162
|
+
|
163
|
+
while(1) {
|
164
|
+
int len = des_read_uint16(des);
|
165
|
+
if(len == 0 && des_read_ahead_byte(des) == AMF0_OBJECT_END_MARKER) {
|
166
|
+
// Don't create a ruby string if this is really the object end
|
167
|
+
des_read_byte(des); // Read type byte
|
168
|
+
return;
|
169
|
+
} else {
|
170
|
+
VALUE key = des_read_string(des, len);
|
171
|
+
char type = des_read_byte(des);
|
172
|
+
rb_hash_aset(hash, key, des0_deserialize(self, type));
|
173
|
+
}
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
static VALUE des0_read_object(VALUE self) {
|
178
|
+
AMF_DESERIALIZER *des;
|
179
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
180
|
+
|
181
|
+
// Create object and add to cache
|
182
|
+
VALUE obj = rb_funcall(des->class_mapper, id_get_ruby_obj, 1, rb_str_new(NULL, 0));
|
183
|
+
rb_ary_push(des->obj_cache, obj);
|
184
|
+
|
185
|
+
// Populate object
|
186
|
+
VALUE props = rb_hash_new();
|
187
|
+
des0_read_props(self, props);
|
188
|
+
rb_funcall(des->class_mapper, id_populate_ruby_obj, 2, obj, props);
|
189
|
+
|
190
|
+
return obj;
|
191
|
+
}
|
192
|
+
|
193
|
+
static VALUE des0_read_typed_object(VALUE self) {
|
194
|
+
AMF_DESERIALIZER *des;
|
195
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
196
|
+
|
197
|
+
// Create object and add to cache
|
198
|
+
VALUE class_name = des_read_string(des, des_read_uint16(des));
|
199
|
+
VALUE obj = rb_funcall(des->class_mapper, id_get_ruby_obj, 1, class_name);
|
200
|
+
rb_ary_push(des->obj_cache, obj);
|
201
|
+
|
202
|
+
// Populate object
|
203
|
+
VALUE props = rb_hash_new();
|
204
|
+
des0_read_props(self, props);
|
205
|
+
rb_funcall(des->class_mapper, id_populate_ruby_obj, 2, obj, props);
|
206
|
+
|
207
|
+
return obj;
|
208
|
+
}
|
209
|
+
|
210
|
+
static VALUE des0_read_hash(VALUE self) {
|
211
|
+
AMF_DESERIALIZER *des;
|
212
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
213
|
+
des_read_uint32(des); // Hash size, but there's no optimization I can perform with this
|
214
|
+
VALUE obj = rb_hash_new();
|
215
|
+
rb_ary_push(des->obj_cache, obj);
|
216
|
+
des0_read_props(self, obj);
|
217
|
+
return obj;
|
218
|
+
}
|
219
|
+
|
220
|
+
static VALUE des0_read_array(VALUE self) {
|
221
|
+
AMF_DESERIALIZER *des;
|
222
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
223
|
+
|
224
|
+
// Limit size of pre-allocation to force remote user to actually send data,
|
225
|
+
// rather than just sending a size of 2**32-1 and nothing afterwards to
|
226
|
+
// crash the server
|
227
|
+
unsigned int len = des_read_uint32(des);
|
228
|
+
VALUE ary = rb_ary_new2(len < MAX_ARRAY_PREALLOC ? len : MAX_ARRAY_PREALLOC);
|
229
|
+
rb_ary_push(des->obj_cache, ary);
|
230
|
+
|
231
|
+
unsigned int i;
|
232
|
+
for(i = 0; i < len; i++) {
|
233
|
+
rb_ary_push(ary, des0_deserialize(self, des_read_byte(des)));
|
234
|
+
}
|
235
|
+
|
236
|
+
return ary;
|
237
|
+
}
|
238
|
+
|
239
|
+
static VALUE des0_read_time(VALUE self) {
|
240
|
+
AMF_DESERIALIZER *des;
|
241
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
242
|
+
double milli = des_read_double(des);
|
243
|
+
des_read_uint16(des); // Timezone - unused
|
244
|
+
time_t sec = milli/1000.0;
|
245
|
+
time_t micro = (milli-sec*1000)*1000;
|
246
|
+
return rb_time_new(sec, micro);
|
247
|
+
}
|
248
|
+
|
249
|
+
/*
|
250
|
+
* Internal C deserialize call. Takes deserializer and a char for the type
|
251
|
+
* marker.
|
252
|
+
*/
|
253
|
+
static VALUE des0_deserialize(VALUE self, char type) {
|
254
|
+
AMF_DESERIALIZER *des;
|
255
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
256
|
+
|
257
|
+
long tmp;
|
258
|
+
VALUE ret = Qnil;
|
259
|
+
switch(type) {
|
260
|
+
case AMF0_STRING_MARKER:
|
261
|
+
ret = des_read_string(des, des_read_uint16(des));
|
262
|
+
break;
|
263
|
+
case AMF0_AMF3_MARKER:
|
264
|
+
ret = des0_read_amf3(self);
|
265
|
+
break;
|
266
|
+
case AMF0_NUMBER_MARKER:
|
267
|
+
ret = rb_float_new(des_read_double(des));
|
268
|
+
break;
|
269
|
+
case AMF0_BOOLEAN_MARKER:
|
270
|
+
ret = des_read_byte(des) == 0 ? Qfalse : Qtrue;
|
271
|
+
break;
|
272
|
+
case AMF0_NULL_MARKER:
|
273
|
+
case AMF0_UNDEFINED_MARKER:
|
274
|
+
case AMF0_UNSUPPORTED_MARKER:
|
275
|
+
ret = Qnil;
|
276
|
+
break;
|
277
|
+
case AMF0_OBJECT_MARKER:
|
278
|
+
ret = des0_read_object(self);
|
279
|
+
break;
|
280
|
+
case AMF0_TYPED_OBJECT_MARKER:
|
281
|
+
ret = des0_read_typed_object(self);
|
282
|
+
break;
|
283
|
+
case AMF0_HASH_MARKER:
|
284
|
+
ret = des0_read_hash(self);
|
285
|
+
break;
|
286
|
+
case AMF0_STRICT_ARRAY_MARKER:
|
287
|
+
ret = des0_read_array(self);
|
288
|
+
break;
|
289
|
+
case AMF0_REFERENCE_MARKER:
|
290
|
+
tmp = des_read_uint16(des);
|
291
|
+
if(tmp >= RARRAY_LEN(des->obj_cache)) rb_raise(rb_eRangeError, "reference index beyond end");
|
292
|
+
ret = RARRAY_PTR(des->obj_cache)[tmp];
|
293
|
+
break;
|
294
|
+
case AMF0_DATE_MARKER:
|
295
|
+
ret = des0_read_time(self);
|
296
|
+
break;
|
297
|
+
case AMF0_XML_MARKER:
|
298
|
+
case AMF0_LONG_STRING_MARKER:
|
299
|
+
ret = des_read_string(des, des_read_uint32(des));
|
300
|
+
break;
|
301
|
+
default:
|
302
|
+
rb_raise(rb_eRuntimeError, "Not supported: %d", type);
|
303
|
+
break;
|
304
|
+
}
|
305
|
+
|
306
|
+
return ret;
|
307
|
+
}
|
308
|
+
|
309
|
+
static VALUE des3_read_string(AMF_DESERIALIZER *des) {
|
310
|
+
int header = des_read_int(des);
|
311
|
+
if((header & 1) == 0) {
|
312
|
+
header >>= 1;
|
313
|
+
if(header >= RARRAY_LEN(des->str_cache)) rb_raise(rb_eRangeError, "str reference index beyond end");
|
314
|
+
return RARRAY_PTR(des->str_cache)[header];
|
315
|
+
} else {
|
316
|
+
VALUE str = des_read_string(des, header >> 1);
|
317
|
+
if(RSTRING_LEN(str) > 0) rb_ary_push(des->str_cache, str);
|
318
|
+
return str;
|
319
|
+
}
|
320
|
+
}
|
321
|
+
|
322
|
+
/*
|
323
|
+
* Same as des3_read_string, but XML uses the object cache, rather than the
|
324
|
+
* string cache
|
325
|
+
*/
|
326
|
+
static VALUE des3_read_xml(VALUE self) {
|
327
|
+
AMF_DESERIALIZER *des;
|
328
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
329
|
+
|
330
|
+
int header = des_read_int(des);
|
331
|
+
if((header & 1) == 0) {
|
332
|
+
header >>= 1;
|
333
|
+
if(header >= RARRAY_LEN(des->obj_cache)) rb_raise(rb_eRangeError, "obj reference index beyond end");
|
334
|
+
return RARRAY_PTR(des->obj_cache)[header];
|
335
|
+
} else {
|
336
|
+
VALUE str = des_read_string(des, header >> 1);
|
337
|
+
if(RSTRING_LEN(str) > 0) rb_ary_push(des->obj_cache, str);
|
338
|
+
return str;
|
339
|
+
}
|
340
|
+
}
|
341
|
+
|
342
|
+
static VALUE des3_read_object(VALUE self) {
|
343
|
+
AMF_DESERIALIZER *des;
|
344
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
345
|
+
|
346
|
+
int header = des_read_int(des);
|
347
|
+
if((header & 1) == 0) {
|
348
|
+
header >>= 1;
|
349
|
+
if(header >= RARRAY_LEN(des->obj_cache)) rb_raise(rb_eRangeError, "obj reference index beyond end");
|
350
|
+
return RARRAY_PTR(des->obj_cache)[header];
|
351
|
+
} else {
|
352
|
+
VALUE externalizable, dynamic, members, class_name, traits;
|
353
|
+
long i, members_len;
|
354
|
+
|
355
|
+
// Parse traits
|
356
|
+
header >>= 1;
|
357
|
+
if((header & 1) == 0) {
|
358
|
+
header >>= 1;
|
359
|
+
if(header >= RARRAY_LEN(des->trait_cache)) rb_raise(rb_eRangeError, "trait reference index beyond end");
|
360
|
+
traits = RARRAY_PTR(des->trait_cache)[header];
|
361
|
+
externalizable = rb_hash_aref(traits, sym_externalizable);
|
362
|
+
dynamic = rb_hash_aref(traits, sym_dynamic);
|
363
|
+
members = rb_hash_aref(traits, sym_members);
|
364
|
+
members_len = members == Qnil ? 0 : RARRAY_LEN(members);
|
365
|
+
class_name = rb_hash_aref(traits, sym_class_name);
|
366
|
+
} else {
|
367
|
+
externalizable = (header & 2) != 0 ? Qtrue : Qfalse;
|
368
|
+
dynamic = (header & 4) != 0 ? Qtrue : Qfalse;
|
369
|
+
members_len = header >> 3;
|
370
|
+
class_name = des3_read_string(des);
|
371
|
+
|
372
|
+
members = rb_ary_new2(members_len);
|
373
|
+
for(i = 0; i < members_len; i++) rb_ary_push(members, des3_read_string(des));
|
374
|
+
|
375
|
+
traits = rb_hash_new();
|
376
|
+
rb_hash_aset(traits, sym_externalizable, externalizable);
|
377
|
+
rb_hash_aset(traits, sym_dynamic, dynamic);
|
378
|
+
rb_hash_aset(traits, sym_members, members);
|
379
|
+
rb_hash_aset(traits, sym_class_name, class_name);
|
380
|
+
rb_ary_push(des->trait_cache, traits);
|
381
|
+
}
|
382
|
+
|
383
|
+
// Optimization for deserializing ArrayCollection
|
384
|
+
if(strcmp(RSTRING_PTR(class_name), "flex.messaging.io.ArrayCollection") == 0) {
|
385
|
+
VALUE arr = des3_deserialize(self); // Adds ArrayCollection array to object cache automatically
|
386
|
+
rb_ary_push(des->obj_cache, arr); // Add again for ArrayCollection source array
|
387
|
+
return arr;
|
388
|
+
}
|
389
|
+
|
390
|
+
VALUE obj = rb_funcall(des->class_mapper, id_get_ruby_obj, 1, class_name);
|
391
|
+
rb_ary_push(des->obj_cache, obj);
|
392
|
+
|
393
|
+
if(externalizable == Qtrue) {
|
394
|
+
rb_funcall(des->src, rb_intern("pos="), 1, LONG2NUM(des->pos)); // Update source StringIO pos
|
395
|
+
rb_funcall(obj, rb_intern("read_external"), 1, self);
|
396
|
+
des->pos = NUM2LONG(rb_funcall(des->src, rb_intern("pos"), 0)); // Update from source
|
397
|
+
return obj;
|
398
|
+
}
|
399
|
+
|
400
|
+
VALUE props = rb_hash_new();
|
401
|
+
for(i = 0; i < members_len; i++) {
|
402
|
+
rb_hash_aset(props, RARRAY_PTR(members)[i], des3_deserialize(self));
|
403
|
+
}
|
404
|
+
|
405
|
+
VALUE dynamic_props = Qnil;
|
406
|
+
if(dynamic == Qtrue) {
|
407
|
+
dynamic_props = rb_hash_new();
|
408
|
+
while(1) {
|
409
|
+
VALUE key = des3_read_string(des);
|
410
|
+
if(RSTRING_LEN(key) == 0) break;
|
411
|
+
rb_hash_aset(dynamic_props, key, des3_deserialize(self));
|
412
|
+
}
|
413
|
+
}
|
414
|
+
|
415
|
+
rb_funcall(des->class_mapper, id_populate_ruby_obj, 3, obj, props, dynamic_props);
|
416
|
+
|
417
|
+
return obj;
|
418
|
+
}
|
419
|
+
}
|
420
|
+
|
421
|
+
static VALUE des3_read_array(VALUE self) {
|
422
|
+
AMF_DESERIALIZER *des;
|
423
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
424
|
+
|
425
|
+
int i;
|
426
|
+
int header = des_read_int(des);
|
427
|
+
if((header & 1) == 0) {
|
428
|
+
header >>= 1;
|
429
|
+
if(header >= RARRAY_LEN(des->obj_cache)) rb_raise(rb_eRangeError, "obj reference index beyond end");
|
430
|
+
return RARRAY_PTR(des->obj_cache)[header];
|
431
|
+
} else {
|
432
|
+
header >>= 1;
|
433
|
+
VALUE obj;
|
434
|
+
VALUE key = des3_read_string(des);
|
435
|
+
if(key == Qnil) rb_raise(rb_eRangeError, "key is Qnil");
|
436
|
+
if(RSTRING_LEN(key) != 0) {
|
437
|
+
obj = rb_hash_new();
|
438
|
+
rb_ary_push(des->obj_cache, obj);
|
439
|
+
while(RSTRING_LEN(key) != 0) {
|
440
|
+
rb_hash_aset(obj, key, des3_deserialize(self));
|
441
|
+
key = des3_read_string(des);
|
442
|
+
}
|
443
|
+
for(i = 0; i < header; i++) {
|
444
|
+
rb_hash_aset(obj, INT2FIX(i), des3_deserialize(self));
|
445
|
+
}
|
446
|
+
} else {
|
447
|
+
// Limit size of pre-allocation to force remote user to actually send data,
|
448
|
+
// rather than just sending a size of 2**32-1 and nothing afterwards to
|
449
|
+
// crash the server
|
450
|
+
obj = rb_ary_new2(header < MAX_ARRAY_PREALLOC ? header : MAX_ARRAY_PREALLOC);
|
451
|
+
rb_ary_push(des->obj_cache, obj);
|
452
|
+
for(i = 0; i < header; i++) {
|
453
|
+
rb_ary_push(obj, des3_deserialize(self));
|
454
|
+
}
|
455
|
+
}
|
456
|
+
return obj;
|
457
|
+
}
|
458
|
+
}
|
459
|
+
|
460
|
+
static VALUE des3_read_time(VALUE self) {
|
461
|
+
AMF_DESERIALIZER *des;
|
462
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
463
|
+
|
464
|
+
int header = des_read_int(des);
|
465
|
+
if((header & 1) == 0) {
|
466
|
+
header >>= 1;
|
467
|
+
if(header >= RARRAY_LEN(des->obj_cache)) rb_raise(rb_eRangeError, "obj reference index beyond end");
|
468
|
+
return RARRAY_PTR(des->obj_cache)[header];
|
469
|
+
} else {
|
470
|
+
double milli = des_read_double(des);
|
471
|
+
time_t sec = milli/1000.0;
|
472
|
+
time_t micro = (milli-sec*1000)*1000;
|
473
|
+
VALUE time = rb_time_new(sec, micro);
|
474
|
+
rb_ary_push(des->obj_cache, time);
|
475
|
+
return time;
|
476
|
+
}
|
477
|
+
}
|
478
|
+
|
479
|
+
static VALUE des3_read_byte_array(VALUE self) {
|
480
|
+
AMF_DESERIALIZER *des;
|
481
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
482
|
+
|
483
|
+
int header = des_read_int(des);
|
484
|
+
if((header & 1) == 0) {
|
485
|
+
header >>= 1;
|
486
|
+
if(header >= RARRAY_LEN(des->obj_cache)) rb_raise(rb_eRangeError, "obj reference index beyond end");
|
487
|
+
return RARRAY_PTR(des->obj_cache)[header];
|
488
|
+
} else {
|
489
|
+
header >>= 1;
|
490
|
+
VALUE args[1] = {des_read_string(des, header)};
|
491
|
+
#ifdef HAVE_RB_STR_ENCODE
|
492
|
+
// Need to force encoding to ASCII-8BIT
|
493
|
+
rb_encoding *ascii = rb_ascii8bit_encoding();
|
494
|
+
rb_enc_associate(args[0], ascii);
|
495
|
+
ENC_CODERANGE_CLEAR(args[0]);
|
496
|
+
#endif
|
497
|
+
VALUE ba = rb_class_new_instance(1, args, cStringIO);
|
498
|
+
rb_ary_push(des->obj_cache, ba);
|
499
|
+
return ba;
|
500
|
+
}
|
501
|
+
}
|
502
|
+
|
503
|
+
static VALUE des3_read_dict(VALUE self) {
|
504
|
+
AMF_DESERIALIZER *des;
|
505
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
506
|
+
|
507
|
+
int header = des_read_int(des);
|
508
|
+
if((header & 1) == 0) {
|
509
|
+
header >>= 1;
|
510
|
+
if(header >= RARRAY_LEN(des->obj_cache)) rb_raise(rb_eRangeError, "obj reference index beyond end");
|
511
|
+
return RARRAY_PTR(des->obj_cache)[header];
|
512
|
+
} else {
|
513
|
+
header >>= 1;
|
514
|
+
|
515
|
+
VALUE dict = rb_hash_new();
|
516
|
+
rb_ary_push(des->obj_cache, dict);
|
517
|
+
|
518
|
+
des_read_byte(des); // Weak Keys: Not supported in ruby
|
519
|
+
|
520
|
+
int i;
|
521
|
+
for(i = 0; i < header; i++) {
|
522
|
+
VALUE key = des3_deserialize(self);
|
523
|
+
VALUE val = des3_deserialize(self);
|
524
|
+
rb_hash_aset(dict, key, val);
|
525
|
+
}
|
526
|
+
|
527
|
+
return dict;
|
528
|
+
}
|
529
|
+
}
|
530
|
+
|
531
|
+
static VALUE des3_read_vec(VALUE self, char type) {
|
532
|
+
AMF_DESERIALIZER *des;
|
533
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
534
|
+
|
535
|
+
int header = des_read_int(des);
|
536
|
+
if((header & 1) == 0) {
|
537
|
+
header >>= 1;
|
538
|
+
if(header >= RARRAY_LEN(des->obj_cache)) rb_raise(rb_eRangeError, "obj reference index beyond end");
|
539
|
+
return RARRAY_PTR(des->obj_cache)[header];
|
540
|
+
} else {
|
541
|
+
header >>= 1;
|
542
|
+
|
543
|
+
// Limit size of pre-allocation to force remote user to actually send data,
|
544
|
+
// rather than just sending a size of 2**32-1 and nothing afterwards to
|
545
|
+
// crash the server
|
546
|
+
VALUE vec = rb_ary_new2(header < MAX_ARRAY_PREALLOC ? header : MAX_ARRAY_PREALLOC);
|
547
|
+
rb_ary_push(des->obj_cache, vec);
|
548
|
+
|
549
|
+
des_read_byte(des); // Fixed Length: Not supported in ruby
|
550
|
+
|
551
|
+
// On 32-bit ARCH, FIXNUM has a limit of 2**31-1, resulting in truncation of large ints/uints
|
552
|
+
int i;
|
553
|
+
switch(type) {
|
554
|
+
case AMF3_VECTOR_INT_MARKER:
|
555
|
+
for(i = 0; i < header; i++) {
|
556
|
+
int ival = des_read_uint32(des);
|
557
|
+
rb_ary_push(vec, INT2FIX(ival));
|
558
|
+
}
|
559
|
+
break;
|
560
|
+
case AMF3_VECTOR_UINT_MARKER:
|
561
|
+
for(i = 0; i < header; i++) {
|
562
|
+
rb_ary_push(vec, INT2FIX(des_read_uint32(des)));
|
563
|
+
}
|
564
|
+
break;
|
565
|
+
case AMF3_VECTOR_DOUBLE_MARKER:
|
566
|
+
for(i = 0; i < header; i++) {
|
567
|
+
rb_ary_push(vec, rb_float_new(des_read_double(des)));
|
568
|
+
}
|
569
|
+
break;
|
570
|
+
case AMF3_VECTOR_OBJECT_MARKER:
|
571
|
+
des3_read_string(des); // Class name of objects - ignored
|
572
|
+
for(i = 0; i < header; i++) {
|
573
|
+
rb_ary_push(vec, des3_deserialize(self));
|
574
|
+
}
|
575
|
+
break;
|
576
|
+
}
|
577
|
+
return vec;
|
578
|
+
}
|
579
|
+
}
|
580
|
+
|
581
|
+
/*
|
582
|
+
* Internal deserialize call - unlike des0_deserialize, it reads the type
|
583
|
+
* itself, due to minor changes in the specs that make that modification
|
584
|
+
* unnecessary.
|
585
|
+
*/
|
586
|
+
static VALUE des3_deserialize(VALUE self) {
|
587
|
+
AMF_DESERIALIZER *des;
|
588
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
589
|
+
|
590
|
+
char type = des_read_byte(des);
|
591
|
+
VALUE ret = Qnil;
|
592
|
+
switch(type) {
|
593
|
+
case AMF3_UNDEFINED_MARKER:
|
594
|
+
case AMF3_NULL_MARKER:
|
595
|
+
ret = Qnil;
|
596
|
+
break;
|
597
|
+
case AMF3_FALSE_MARKER:
|
598
|
+
ret = Qfalse;
|
599
|
+
break;
|
600
|
+
case AMF3_TRUE_MARKER:
|
601
|
+
ret = Qtrue;
|
602
|
+
break;
|
603
|
+
case AMF3_INTEGER_MARKER:
|
604
|
+
ret = INT2FIX(des_read_int(des));
|
605
|
+
break;
|
606
|
+
case AMF3_DOUBLE_MARKER:
|
607
|
+
ret = rb_float_new(des_read_double(des));
|
608
|
+
break;
|
609
|
+
case AMF3_STRING_MARKER:
|
610
|
+
ret = des3_read_string(des);
|
611
|
+
break;
|
612
|
+
case AMF3_ARRAY_MARKER:
|
613
|
+
ret = des3_read_array(self);
|
614
|
+
break;
|
615
|
+
case AMF3_OBJECT_MARKER:
|
616
|
+
ret = des3_read_object(self);
|
617
|
+
break;
|
618
|
+
case AMF3_DATE_MARKER:
|
619
|
+
ret = des3_read_time(self);
|
620
|
+
break;
|
621
|
+
case AMF3_XML_DOC_MARKER:
|
622
|
+
case AMF3_XML_MARKER:
|
623
|
+
ret = des3_read_xml(self);
|
624
|
+
break;
|
625
|
+
case AMF3_BYTE_ARRAY_MARKER:
|
626
|
+
ret = des3_read_byte_array(self);
|
627
|
+
break;
|
628
|
+
case AMF3_VECTOR_INT_MARKER:
|
629
|
+
case AMF3_VECTOR_UINT_MARKER:
|
630
|
+
case AMF3_VECTOR_DOUBLE_MARKER:
|
631
|
+
case AMF3_VECTOR_OBJECT_MARKER:
|
632
|
+
ret = des3_read_vec(self, type);
|
633
|
+
break;
|
634
|
+
case AMF3_DICT_MARKER:
|
635
|
+
ret = des3_read_dict(self);
|
636
|
+
break;
|
637
|
+
default:
|
638
|
+
rb_raise(rb_eRuntimeError, "Not supported: %d", type);
|
639
|
+
break;
|
640
|
+
}
|
641
|
+
|
642
|
+
return ret;
|
643
|
+
}
|
644
|
+
|
645
|
+
/*
|
646
|
+
* Mark the reader and its source. If caches are populated mark them as well.
|
647
|
+
*/
|
648
|
+
static void des_mark(AMF_DESERIALIZER *des) {
|
649
|
+
if(!des) return;
|
650
|
+
rb_gc_mark(des->class_mapper);
|
651
|
+
rb_gc_mark(des->src);
|
652
|
+
if(des->obj_cache) rb_gc_mark(des->obj_cache);
|
653
|
+
if(des->str_cache) rb_gc_mark(des->str_cache);
|
654
|
+
if(des->trait_cache) rb_gc_mark(des->trait_cache);
|
655
|
+
}
|
656
|
+
|
657
|
+
/*
|
658
|
+
* Free the reader. Don't need to free anything but the struct because we didn't
|
659
|
+
* alloc anything - source is from the ruby source object.
|
660
|
+
*/
|
661
|
+
static void des_free(AMF_DESERIALIZER *des) {
|
662
|
+
xfree(des);
|
663
|
+
}
|
664
|
+
|
665
|
+
/*
|
666
|
+
* Create new struct and wrap with class
|
667
|
+
*/
|
668
|
+
static VALUE des_alloc(VALUE klass) {
|
669
|
+
AMF_DESERIALIZER *des = ALLOC(AMF_DESERIALIZER);
|
670
|
+
memset(des, 0, sizeof(AMF_DESERIALIZER));
|
671
|
+
return Data_Wrap_Struct(klass, des_mark, des_free, des);
|
672
|
+
}
|
673
|
+
|
674
|
+
/*
|
675
|
+
* Initializer
|
676
|
+
*/
|
677
|
+
static VALUE des_initialize(VALUE self, VALUE class_mapper) {
|
678
|
+
AMF_DESERIALIZER *des;
|
679
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
680
|
+
des->class_mapper = class_mapper;
|
681
|
+
return self;
|
682
|
+
}
|
683
|
+
|
684
|
+
/*
|
685
|
+
* call-seq:
|
686
|
+
* ser.stream => StringIO
|
687
|
+
*
|
688
|
+
* Returns the source that the deserializer is reading from
|
689
|
+
*/
|
690
|
+
static VALUE des_source(VALUE self) {
|
691
|
+
AMF_DESERIALIZER *des;
|
692
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
693
|
+
return des->src;
|
694
|
+
}
|
695
|
+
|
696
|
+
/*
|
697
|
+
* call-seq:
|
698
|
+
* des.deserialize(amf_ver, str) => obj
|
699
|
+
* des.deserialize(amf_ver, StringIO) => obj
|
700
|
+
*
|
701
|
+
* Deserialize the string or StringIO from AMF to a ruby object.
|
702
|
+
*/
|
703
|
+
VALUE des_deserialize(VALUE self, VALUE ver, VALUE src) {
|
704
|
+
AMF_DESERIALIZER *des;
|
705
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
706
|
+
|
707
|
+
// Process version
|
708
|
+
int int_ver = FIX2INT(ver);
|
709
|
+
if(int_ver != 0 && int_ver != 3) rb_raise(rb_eArgError, "unsupported version %d", int_ver);
|
710
|
+
des->version = int_ver;
|
711
|
+
|
712
|
+
// Process source
|
713
|
+
if(src != Qnil) {
|
714
|
+
des_set_src(des, src);
|
715
|
+
} else if(!des->src) {
|
716
|
+
rb_raise(rb_eArgError, "Missing deserialization source");
|
717
|
+
}
|
718
|
+
|
719
|
+
// Deserialize from source
|
720
|
+
VALUE ret;
|
721
|
+
if(des->version == 0) {
|
722
|
+
des->obj_cache = rb_ary_new();
|
723
|
+
ret = des0_deserialize(self, des_read_byte(des));
|
724
|
+
} else {
|
725
|
+
des->obj_cache = rb_ary_new();
|
726
|
+
des->str_cache = rb_ary_new();
|
727
|
+
des->trait_cache = rb_ary_new();
|
728
|
+
ret = des3_deserialize(self);
|
729
|
+
}
|
730
|
+
|
731
|
+
// Update source position
|
732
|
+
rb_funcall(des->src, rb_intern("pos="), 1, LONG2NUM(des->pos)); // Update source StringIO pos
|
733
|
+
|
734
|
+
return ret;
|
735
|
+
}
|
736
|
+
|
737
|
+
/*
|
738
|
+
* call-seq:
|
739
|
+
* des.read_object => obj
|
740
|
+
*
|
741
|
+
* Reads an object from the deserializer's stream and returns it.
|
742
|
+
*/
|
743
|
+
VALUE des_read_object(VALUE self) {
|
744
|
+
AMF_DESERIALIZER *des;
|
745
|
+
Data_Get_Struct(self, AMF_DESERIALIZER, des);
|
746
|
+
|
747
|
+
// Update internal pos from source in case they've modified it
|
748
|
+
des->pos = NUM2LONG(rb_funcall(des->src, rb_intern("pos"), 0));
|
749
|
+
|
750
|
+
// Deserialize
|
751
|
+
VALUE ret;
|
752
|
+
if(des->version == 0) {
|
753
|
+
ret = des0_deserialize(self, des_read_byte(des));
|
754
|
+
} else {
|
755
|
+
ret = des3_deserialize(self);
|
756
|
+
}
|
757
|
+
|
758
|
+
// Update source position
|
759
|
+
rb_funcall(des->src, rb_intern("pos="), 1, LONG2NUM(des->pos)); // Update source StringIO pos
|
760
|
+
|
761
|
+
return ret;
|
762
|
+
}
|
763
|
+
|
764
|
+
void Init_rocket_amf_deserializer() {
|
765
|
+
// Define Deserializer
|
766
|
+
cDeserializer = rb_define_class_under(mRocketAMFExt, "Deserializer", rb_cObject);
|
767
|
+
rb_define_alloc_func(cDeserializer, des_alloc);
|
768
|
+
rb_define_method(cDeserializer, "initialize", des_initialize, 1);
|
769
|
+
rb_define_method(cDeserializer, "source", des_source, 0);
|
770
|
+
rb_define_method(cDeserializer, "deserialize", des_deserialize, 2);
|
771
|
+
rb_define_method(cDeserializer, "read_object", des_read_object, 0);
|
772
|
+
|
773
|
+
// Get refs to commonly used symbols and ids
|
774
|
+
id_get_ruby_obj = rb_intern("get_ruby_obj");
|
775
|
+
id_populate_ruby_obj = rb_intern("populate_ruby_obj");
|
776
|
+
}
|