ruby_sol 0.1

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 (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