ruby_sol 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +90 -0
  3. data/Rakefile +43 -0
  4. data/lib/ruby_sol.rb +113 -0
  5. data/lib/ruby_sol/class_mapping.rb +250 -0
  6. data/lib/ruby_sol/constants.rb +50 -0
  7. data/lib/ruby_sol/extensions.rb +22 -0
  8. data/lib/ruby_sol/pure/deserializer.rb +461 -0
  9. data/lib/ruby_sol/pure/io_helpers.rb +94 -0
  10. data/lib/ruby_sol/pure/serializer.rb +490 -0
  11. data/spec/class_mapping_spec.rb +110 -0
  12. data/spec/deserializer_spec.rb +449 -0
  13. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  14. data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
  15. data/spec/fixtures/objects/amf0-date.bin +0 -0
  16. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  17. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  18. data/spec/fixtures/objects/amf0-null.bin +1 -0
  19. data/spec/fixtures/objects/amf0-number.bin +0 -0
  20. data/spec/fixtures/objects/amf0-object.bin +0 -0
  21. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  22. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  23. data/spec/fixtures/objects/amf0-string.bin +0 -0
  24. data/spec/fixtures/objects/amf0-time.bin +0 -0
  25. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  26. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  27. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  28. data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
  29. data/spec/fixtures/objects/amf3-0.bin +0 -0
  30. data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
  31. data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
  32. data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
  33. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  34. data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
  35. data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
  36. data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
  37. data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
  38. data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
  39. data/spec/fixtures/objects/amf3-date.bin +0 -0
  40. data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
  41. data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
  42. data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
  43. data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
  44. data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
  45. data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
  46. data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
  47. data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
  48. data/spec/fixtures/objects/amf3-false.bin +1 -0
  49. data/spec/fixtures/objects/amf3-float.bin +0 -0
  50. data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
  51. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  52. data/spec/fixtures/objects/amf3-large-max.bin +0 -0
  53. data/spec/fixtures/objects/amf3-large-min.bin +0 -0
  54. data/spec/fixtures/objects/amf3-max.bin +1 -0
  55. data/spec/fixtures/objects/amf3-min.bin +0 -0
  56. data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
  57. data/spec/fixtures/objects/amf3-null.bin +1 -0
  58. data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
  59. data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
  60. data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
  61. data/spec/fixtures/objects/amf3-string.bin +1 -0
  62. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  63. data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
  64. data/spec/fixtures/objects/amf3-true.bin +1 -0
  65. data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
  66. data/spec/fixtures/objects/amf3-vector-double.bin +0 -0
  67. data/spec/fixtures/objects/amf3-vector-int.bin +0 -0
  68. data/spec/fixtures/objects/amf3-vector-object.bin +0 -0
  69. data/spec/fixtures/objects/amf3-vector-uint.bin +0 -0
  70. data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
  71. data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
  72. data/spec/fixtures/objects/amf3-xml.bin +1 -0
  73. data/spec/serializer_spec.rb +503 -0
  74. data/spec/spec_helper.rb +59 -0
  75. metadata +128 -0
@@ -0,0 +1,50 @@
1
+ module RubySol
2
+ # AMF0 Type Markers
3
+ AMF0_NUMBER_MARKER = 0x00 #"\000"
4
+ AMF0_BOOLEAN_MARKER = 0x01 #"\001"
5
+ AMF0_STRING_MARKER = 0x02 #"\002"
6
+ AMF0_OBJECT_MARKER = 0x03 #"\003"
7
+ AMF0_MOVIE_CLIP_MARKER = 0x04 #"\004" # Unused
8
+ AMF0_NULL_MARKER = 0x05 #"\005"
9
+ AMF0_UNDEFINED_MARKER = 0x06 #"\006"
10
+ AMF0_REFERENCE_MARKER = 0x07 #"\a"
11
+ AMF0_HASH_MARKER = 0x08 #"\b"
12
+ AMF0_OBJECT_END_MARKER = 0x09 #"\t"
13
+ AMF0_STRICT_ARRAY_MARKER = 0x0A #"\n"
14
+ AMF0_DATE_MARKER = 0x0B #"\v"
15
+ AMF0_LONG_STRING_MARKER = 0x0C #"\f"
16
+ AMF0_UNSUPPORTED_MARKER = 0x0D #"\r"
17
+ AMF0_RECORDSET_MARKER = 0x0E #"\016" # Unused
18
+ AMF0_XML_MARKER = 0x0F #"\017"
19
+ AMF0_TYPED_OBJECT_MARKER = 0x10 #"\020"
20
+ AMF0_AMF3_MARKER = 0x11 #"\021"
21
+
22
+ # AMF3 Type Markers
23
+ AMF3_UNDEFINED_MARKER = 0x00 #"\000"
24
+ AMF3_NULL_MARKER = 0x01 #"\001"
25
+ AMF3_FALSE_MARKER = 0x02 #"\002"
26
+ AMF3_TRUE_MARKER = 0x03 #"\003"
27
+ AMF3_INTEGER_MARKER = 0x04 #"\004"
28
+ AMF3_DOUBLE_MARKER = 0x05 #"\005"
29
+ AMF3_STRING_MARKER = 0x06 #"\006"
30
+ AMF3_XML_DOC_MARKER = 0x07 #"\a"
31
+ AMF3_DATE_MARKER = 0x08 #"\b"
32
+ AMF3_ARRAY_MARKER = 0x09 #"\t"
33
+ AMF3_OBJECT_MARKER = 0x0A #"\n"
34
+ AMF3_XML_MARKER = 0x0B #"\v"
35
+ AMF3_BYTE_ARRAY_MARKER = 0x0C #"\f"
36
+ AMF3_VECTOR_INT_MARKER = 0x0D #"\r"
37
+ AMF3_VECTOR_UINT_MARKER = 0x0E #"\016"
38
+ AMF3_VECTOR_DOUBLE_MARKER = 0x0F #"\017"
39
+ AMF3_VECTOR_OBJECT_MARKER = 0x10 #"\020"
40
+ AMF3_DICT_MARKER = 0x11 #"\021"
41
+
42
+ # Other AMF3 Markers
43
+ AMF3_EMPTY_STRING = 0x01
44
+ AMF3_CLOSE_DYNAMIC_OBJECT = 0x01
45
+ AMF3_CLOSE_DYNAMIC_ARRAY = 0x01
46
+
47
+ # Other Constants
48
+ MAX_INTEGER = 268435455
49
+ MIN_INTEGER = -268435456
50
+ end
@@ -0,0 +1,22 @@
1
+ # Joc's monkeypatch for string bytesize (only available in 1.8.7+)
2
+ if !"amf".respond_to? :bytesize
3
+ class String #:nodoc:
4
+ def bytesize
5
+ self.size
6
+ end
7
+ end
8
+ end
9
+
10
+ # Add <tt>ArrayCollection</tt> override to arrays
11
+ class Array
12
+ # Override <tt>RubySol::ClassMapper.use_array_collection</tt> setting for
13
+ # this array. Adds <tt>is_array_collection?</tt> method, which is used by the
14
+ # serializer over the global config if defined.
15
+ def is_array_collection= a
16
+ @is_array_collection = a
17
+
18
+ def self.is_array_collection? #:nodoc:
19
+ @is_array_collection
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,461 @@
1
+ require 'ruby_sol/pure/io_helpers'
2
+
3
+ module RubySol
4
+ module Pure
5
+ # Pure ruby deserializer for AMF0 and AMF3
6
+ class Deserializer
7
+ attr_accessor :source
8
+
9
+ # Pass in the class mapper instance to use when deserializing. This
10
+ # enables better caching behavior in the class mapper and allows
11
+ # one to change mappings between deserialization attempts.
12
+ def initialize class_mapper
13
+ @class_mapper = class_mapper
14
+ reset_caches()
15
+ end
16
+
17
+ def reset_caches
18
+ @ref_cache = []
19
+ @string_cache = []
20
+ @object_cache = []
21
+ @trait_cache = []
22
+ end
23
+
24
+ # Deserialize the source using AMF0 or AMF3. Source should either
25
+ # be a string or StringIO object. If you pass a StringIO object,
26
+ # it will have its position updated to the end of the deserialized
27
+ # data.
28
+ def deserialize version, source
29
+ raise ArgumentError, "unsupported version #{version}" unless [0,3].include?(version)
30
+ @version = version
31
+
32
+ if StringIO === source
33
+ @source = source
34
+ elsif source
35
+ @source = StringIO.new(source)
36
+ elsif @source.nil?
37
+ raise AMFError, "no source to deserialize"
38
+ end
39
+
40
+ reset_caches()
41
+
42
+ if @version == 0
43
+ return amf0_deserialize
44
+ else
45
+ return amf3_deserialize
46
+ end
47
+ end
48
+
49
+ # Reads an object from the deserializer's stream and returns it.
50
+ def read_object
51
+ if @version == 0
52
+ return amf0_deserialize
53
+ else
54
+ return amf3_deserialize
55
+ end
56
+ end
57
+
58
+ # private
59
+ include RubySol::Pure::ReadIOHelpers
60
+
61
+ def amf0_deserialize type=nil
62
+ type = read_int8 @source unless type
63
+ case type
64
+ when AMF0_NUMBER_MARKER
65
+ amf0_read_number
66
+ when AMF0_BOOLEAN_MARKER
67
+ amf0_read_boolean
68
+ when AMF0_STRING_MARKER
69
+ amf0_read_string
70
+ when AMF0_OBJECT_MARKER
71
+ amf0_read_object
72
+ when AMF0_NULL_MARKER
73
+ nil
74
+ when AMF0_UNDEFINED_MARKER
75
+ nil
76
+ when AMF0_REFERENCE_MARKER
77
+ amf0_read_reference
78
+ when AMF0_HASH_MARKER
79
+ amf0_read_hash
80
+ when AMF0_STRICT_ARRAY_MARKER
81
+ amf0_read_array
82
+ when AMF0_DATE_MARKER
83
+ amf0_read_date
84
+ when AMF0_LONG_STRING_MARKER
85
+ amf0_read_string true
86
+ when AMF0_UNSUPPORTED_MARKER
87
+ nil
88
+ when AMF0_XML_MARKER
89
+ amf0_read_string true
90
+ when AMF0_TYPED_OBJECT_MARKER
91
+ amf0_read_typed_object
92
+ when AMF0_AMF3_MARKER
93
+ deserialize(3, nil)
94
+ else
95
+ raise AMFError, "Invalid type: #{type}"
96
+ end
97
+ end
98
+
99
+ def amf0_read_number
100
+ res = read_double @source
101
+ (res.is_a?(Float) && res.nan?) ? nil : res # check for NaN and convert them to nil
102
+ end
103
+
104
+ def amf0_read_boolean
105
+ read_int8(@source) != 0
106
+ end
107
+
108
+ def amf0_read_string long=false
109
+ len = long ? read_word32_network(@source) : read_word16_network(@source)
110
+ str = @source.read(len)
111
+ str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
112
+ str
113
+ end
114
+
115
+ def amf0_read_reference
116
+ index = read_word16_network(@source)
117
+ @ref_cache[index]
118
+ end
119
+
120
+ def amf0_read_array
121
+ len = read_word32_network(@source)
122
+ array = []
123
+ @ref_cache << array
124
+
125
+ 0.upto(len - 1) do
126
+ array << amf0_deserialize
127
+ end
128
+ array
129
+ end
130
+
131
+ def amf0_read_date
132
+ seconds = read_double(@source).to_f/1000
133
+ time = Time.at(seconds)
134
+ tz = read_word16_network(@source) # Unused
135
+ time
136
+ end
137
+
138
+ def amf0_read_props obj={}
139
+ while true
140
+ key = amf0_read_string
141
+ type = read_int8 @source
142
+ break if type == AMF0_OBJECT_END_MARKER
143
+ obj[key] = amf0_deserialize(type)
144
+ end
145
+ obj
146
+ end
147
+
148
+ def amf0_read_hash
149
+ len = read_word32_network(@source) # Read and ignore length
150
+ obj = {}
151
+ @ref_cache << obj
152
+ amf0_read_props obj
153
+ end
154
+
155
+ def amf0_read_object add_to_ref_cache=true
156
+ # Create "object" and add to ref cache (it's always a Hash)
157
+ obj = @class_mapper.get_ruby_obj ""
158
+ @ref_cache << obj
159
+
160
+ # Populate object
161
+ props = amf0_read_props
162
+ @class_mapper.populate_ruby_obj obj, props
163
+ return obj
164
+ end
165
+
166
+ def amf0_read_typed_object
167
+ # Create object to add to ref cache
168
+ class_name = amf0_read_string
169
+ obj = @class_mapper.get_ruby_obj class_name
170
+ @ref_cache << obj
171
+
172
+ # Populate object
173
+ props = amf0_read_props
174
+ @class_mapper.populate_ruby_obj obj, props
175
+ return obj
176
+ end
177
+
178
+ def amf3_deserialize
179
+ type = read_int8 @source
180
+ case type
181
+ when AMF3_UNDEFINED_MARKER
182
+ nil
183
+ when AMF3_NULL_MARKER
184
+ nil
185
+ when AMF3_FALSE_MARKER
186
+ false
187
+ when AMF3_TRUE_MARKER
188
+ true
189
+ when AMF3_INTEGER_MARKER
190
+ amf3_read_integer
191
+ when AMF3_DOUBLE_MARKER
192
+ amf3_read_number
193
+ when AMF3_STRING_MARKER
194
+ amf3_read_string
195
+ when AMF3_XML_DOC_MARKER, AMF3_XML_MARKER
196
+ amf3_read_xml
197
+ when AMF3_DATE_MARKER
198
+ amf3_read_date
199
+ when AMF3_ARRAY_MARKER
200
+ amf3_read_array
201
+ when AMF3_OBJECT_MARKER
202
+ amf3_read_object
203
+ when AMF3_BYTE_ARRAY_MARKER
204
+ amf3_read_byte_array
205
+ when AMF3_VECTOR_INT_MARKER, AMF3_VECTOR_UINT_MARKER, AMF3_VECTOR_DOUBLE_MARKER, AMF3_VECTOR_OBJECT_MARKER
206
+ amf3_read_vector type
207
+ when AMF3_DICT_MARKER
208
+ amf3_read_dict
209
+ else
210
+ raise AMFError, "Invalid type: #{type}"
211
+ end
212
+ end
213
+
214
+ def amf3_read_integer
215
+ n = 0
216
+ b = read_word8(@source) || 0
217
+ result = 0
218
+
219
+ while ((b & 0x80) != 0 && n < 3)
220
+ result = result << 7
221
+ result = result | (b & 0x7f)
222
+ b = read_word8(@source) || 0
223
+ n = n + 1
224
+ end
225
+
226
+ if (n < 3)
227
+ result = result << 7
228
+ result = result | b
229
+ else
230
+ #Use all 8 bits from the 4th byte
231
+ result = result << 8
232
+ result = result | b
233
+
234
+ #Check if the integer should be negative
235
+ if (result > MAX_INTEGER)
236
+ result -= (1 << 29)
237
+ end
238
+ end
239
+ result
240
+ end
241
+
242
+ def amf3_read_number
243
+ res = read_double @source
244
+ (res.is_a?(Float) && res.nan?) ? nil : res # check for NaN and convert them to nil
245
+ end
246
+
247
+ def amf3_read_string
248
+ type = amf3_read_integer
249
+ is_reference = (type & 0x01) == 0
250
+
251
+ if is_reference
252
+ reference = type >> 1
253
+ return @string_cache[reference]
254
+ else
255
+ length = type >> 1
256
+ str = ""
257
+ if length > 0
258
+ str = @source.read(length)
259
+ str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
260
+ @string_cache << str
261
+ end
262
+ return str
263
+ end
264
+ end
265
+
266
+ def amf3_read_xml
267
+ type = amf3_read_integer
268
+ is_reference = (type & 0x01) == 0
269
+
270
+ if is_reference
271
+ reference = type >> 1
272
+ return @object_cache[reference]
273
+ else
274
+ length = type >> 1
275
+ str = ""
276
+ if length > 0
277
+ str = @source.read(length)
278
+ str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
279
+ @object_cache << str
280
+ end
281
+ return str
282
+ end
283
+ end
284
+
285
+ def amf3_read_byte_array
286
+ type = amf3_read_integer
287
+ is_reference = (type & 0x01) == 0
288
+
289
+ if is_reference
290
+ reference = type >> 1
291
+ return @object_cache[reference]
292
+ else
293
+ length = type >> 1
294
+ obj = StringIO.new @source.read(length)
295
+ @object_cache << obj
296
+ obj
297
+ end
298
+ end
299
+
300
+ def amf3_read_array
301
+ type = amf3_read_integer
302
+ is_reference = (type & 0x01) == 0
303
+
304
+ if is_reference
305
+ reference = type >> 1
306
+ return @object_cache[reference]
307
+ else
308
+ length = type >> 1
309
+ property_name = amf3_read_string
310
+ array = property_name.length > 0 ? {} : []
311
+ @object_cache << array
312
+
313
+ while property_name.length > 0
314
+ value = amf3_deserialize
315
+ array[property_name] = value
316
+ property_name = amf3_read_string
317
+ end
318
+ 0.upto(length - 1) {|i| array[i] = amf3_deserialize }
319
+
320
+ array
321
+ end
322
+ end
323
+
324
+ def amf3_read_object
325
+ type = amf3_read_integer
326
+ is_reference = (type & 0x01) == 0
327
+
328
+ if is_reference
329
+ reference = type >> 1
330
+ return @object_cache[reference]
331
+ else
332
+ class_type = type >> 1
333
+ class_is_reference = (class_type & 0x01) == 0
334
+
335
+ if class_is_reference
336
+ reference = class_type >> 1
337
+ traits = @trait_cache[reference]
338
+ else
339
+ externalizable = (class_type & 0x02) != 0
340
+ dynamic = (class_type & 0x04) != 0
341
+ attribute_count = class_type >> 3
342
+ class_name = amf3_read_string
343
+
344
+ class_attributes = []
345
+ attribute_count.times{class_attributes << amf3_read_string} # Read class members
346
+
347
+ traits = {
348
+ :class_name => class_name,
349
+ :members => class_attributes,
350
+ :externalizable => externalizable,
351
+ :dynamic => dynamic
352
+ }
353
+ @trait_cache << traits
354
+ end
355
+
356
+ # Optimization for deserializing ArrayCollection
357
+ if traits[:class_name] == "flex.messaging.io.ArrayCollection"
358
+ arr = amf3_deserialize # Adds ArrayCollection array to object cache
359
+ @object_cache << arr # Add again for ArrayCollection source array
360
+ return arr
361
+ end
362
+
363
+ obj = @class_mapper.get_ruby_obj traits[:class_name]
364
+ @object_cache << obj
365
+
366
+ if traits[:externalizable]
367
+ obj.read_external self
368
+ else
369
+ props = {}
370
+ traits[:members].each do |key|
371
+ value = amf3_deserialize
372
+ props[key] = value
373
+ end
374
+
375
+ dynamic_props = nil
376
+ if traits[:dynamic]
377
+ dynamic_props = {}
378
+ while (key = amf3_read_string) && key.length != 0 do # read next key
379
+ value = amf3_deserialize
380
+ dynamic_props[key] = value
381
+ end
382
+ end
383
+
384
+ @class_mapper.populate_ruby_obj obj, props, dynamic_props
385
+ end
386
+ obj
387
+ end
388
+ end
389
+
390
+ def amf3_read_date
391
+ type = amf3_read_integer
392
+ is_reference = (type & 0x01) == 0
393
+ if is_reference
394
+ reference = type >> 1
395
+ return @object_cache[reference]
396
+ else
397
+ seconds = read_double(@source).to_f/1000
398
+ time = Time.at(seconds)
399
+ @object_cache << time
400
+ time
401
+ end
402
+ end
403
+
404
+ def amf3_read_dict
405
+ type = amf3_read_integer
406
+ is_reference = (type & 0x01) == 0
407
+ if is_reference
408
+ reference = type >> 1
409
+ return @object_cache[reference]
410
+ else
411
+ dict = {}
412
+ @object_cache << dict
413
+ length = type >> 1
414
+ weak_keys = read_int8 @source # Ignore: Not supported in ruby
415
+ 0.upto(length - 1) do |i|
416
+ dict[amf3_deserialize] = amf3_deserialize
417
+ end
418
+ dict
419
+ end
420
+ end
421
+
422
+ def amf3_read_vector vector_type
423
+ type = amf3_read_integer
424
+ is_reference = (type & 0x01) == 0
425
+ if is_reference
426
+ reference = type >> 1
427
+ return @object_cache[reference]
428
+ else
429
+ vec = []
430
+ @object_cache << vec
431
+ length = type >> 1
432
+ fixed_vector = read_int8 @source # Ignore
433
+ case vector_type
434
+ when AMF3_VECTOR_INT_MARKER
435
+ 0.upto(length - 1) do |i|
436
+ int = read_word32_network(@source)
437
+ int = int - 2**32 if int > MAX_INTEGER
438
+ vec << int
439
+ end
440
+ when AMF3_VECTOR_UINT_MARKER
441
+ 0.upto(length - 1) do |i|
442
+ vec << read_word32_network(@source)
443
+ # puts vec[i].to_s(2)
444
+ end
445
+ when AMF3_VECTOR_DOUBLE_MARKER
446
+ 0.upto(length - 1) do |i|
447
+ vec << amf3_read_number
448
+ end
449
+ when AMF3_VECTOR_OBJECT_MARKER
450
+ vector_class = amf3_read_string # Ignore
451
+ # puts vector_class
452
+ 0.upto(length - 1) do |i|
453
+ vec << amf3_deserialize
454
+ end
455
+ end
456
+ vec
457
+ end
458
+ end
459
+ end
460
+ end
461
+ end