scotttam-RocketAMF 0.2.2

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. data/README.rdoc +45 -0
  2. data/Rakefile +54 -0
  3. data/lib/rocketamf.rb +128 -0
  4. data/lib/rocketamf/class_mapping.rb +231 -0
  5. data/lib/rocketamf/constants.rb +46 -0
  6. data/lib/rocketamf/pure.rb +28 -0
  7. data/lib/rocketamf/pure/deserializer.rb +419 -0
  8. data/lib/rocketamf/pure/io_helpers.rb +94 -0
  9. data/lib/rocketamf/pure/remoting.rb +134 -0
  10. data/lib/rocketamf/pure/serializer.rb +433 -0
  11. data/lib/rocketamf/remoting.rb +144 -0
  12. data/lib/rocketamf/values/array_collection.rb +13 -0
  13. data/lib/rocketamf/values/messages.rb +133 -0
  14. data/lib/rocketamf/values/typed_hash.rb +13 -0
  15. data/spec/amf/class_mapping_spec.rb +150 -0
  16. data/spec/amf/deserializer_spec.rb +367 -0
  17. data/spec/amf/remoting_spec.rb +132 -0
  18. data/spec/amf/serializer_spec.rb +384 -0
  19. data/spec/amf/values/array_collection_spec.rb +19 -0
  20. data/spec/amf/values/messages_spec.rb +31 -0
  21. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  22. data/spec/fixtures/objects/amf0-complexEncodedStringArray.bin +0 -0
  23. data/spec/fixtures/objects/amf0-date.bin +0 -0
  24. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  25. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  26. data/spec/fixtures/objects/amf0-null.bin +1 -0
  27. data/spec/fixtures/objects/amf0-number.bin +0 -0
  28. data/spec/fixtures/objects/amf0-object.bin +0 -0
  29. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  30. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  31. data/spec/fixtures/objects/amf0-string.bin +0 -0
  32. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  33. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  34. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  35. data/spec/fixtures/objects/amf0-xmlDoc.bin +0 -0
  36. data/spec/fixtures/objects/amf3-0.bin +0 -0
  37. data/spec/fixtures/objects/amf3-arrayCollection.bin +2 -0
  38. data/spec/fixtures/objects/amf3-arrayRef.bin +1 -0
  39. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  40. data/spec/fixtures/objects/amf3-byteArray.bin +0 -0
  41. data/spec/fixtures/objects/amf3-byteArrayRef.bin +1 -0
  42. data/spec/fixtures/objects/amf3-complexEncodedStringArray.bin +1 -0
  43. data/spec/fixtures/objects/amf3-date.bin +0 -0
  44. data/spec/fixtures/objects/amf3-datesRef.bin +0 -0
  45. data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
  46. data/spec/fixtures/objects/amf3-dynObject.bin +2 -0
  47. data/spec/fixtures/objects/amf3-emptyArray.bin +1 -0
  48. data/spec/fixtures/objects/amf3-emptyArrayRef.bin +1 -0
  49. data/spec/fixtures/objects/amf3-emptyDictionary.bin +0 -0
  50. data/spec/fixtures/objects/amf3-emptyStringRef.bin +1 -0
  51. data/spec/fixtures/objects/amf3-encodedStringRef.bin +0 -0
  52. data/spec/fixtures/objects/amf3-false.bin +1 -0
  53. data/spec/fixtures/objects/amf3-float.bin +0 -0
  54. data/spec/fixtures/objects/amf3-graphMember.bin +0 -0
  55. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  56. data/spec/fixtures/objects/amf3-largeMax.bin +0 -0
  57. data/spec/fixtures/objects/amf3-largeMin.bin +0 -0
  58. data/spec/fixtures/objects/amf3-max.bin +1 -0
  59. data/spec/fixtures/objects/amf3-min.bin +0 -0
  60. data/spec/fixtures/objects/amf3-mixedArray.bin +11 -0
  61. data/spec/fixtures/objects/amf3-null.bin +1 -0
  62. data/spec/fixtures/objects/amf3-objRef.bin +0 -0
  63. data/spec/fixtures/objects/amf3-primArray.bin +1 -0
  64. data/spec/fixtures/objects/amf3-string.bin +1 -0
  65. data/spec/fixtures/objects/amf3-stringRef.bin +0 -0
  66. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  67. data/spec/fixtures/objects/amf3-traitRef.bin +3 -0
  68. data/spec/fixtures/objects/amf3-true.bin +1 -0
  69. data/spec/fixtures/objects/amf3-typedObject.bin +2 -0
  70. data/spec/fixtures/objects/amf3-xml.bin +1 -0
  71. data/spec/fixtures/objects/amf3-xmlDoc.bin +1 -0
  72. data/spec/fixtures/objects/amf3-xmlRef.bin +1 -0
  73. data/spec/fixtures/request/acknowledge-response.bin +0 -0
  74. data/spec/fixtures/request/amf0-error-response.bin +0 -0
  75. data/spec/fixtures/request/commandMessage.bin +0 -0
  76. data/spec/fixtures/request/remotingMessage.bin +0 -0
  77. data/spec/fixtures/request/simple-response.bin +0 -0
  78. data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
  79. data/spec/spec.opts +1 -0
  80. data/spec/spec_helper.rb +31 -0
  81. metadata +153 -0
@@ -0,0 +1,46 @@
1
+ module RocketAMF
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_DICT_MARKER = 0x11 #"\021"
37
+
38
+ # Other AMF3 Markers
39
+ AMF3_EMPTY_STRING = 0x01
40
+ AMF3_CLOSE_DYNAMIC_OBJECT = 0x01
41
+ AMF3_CLOSE_DYNAMIC_ARRAY = 0x01
42
+
43
+ # Other Constants
44
+ MAX_INTEGER = 268435455
45
+ MIN_INTEGER = -268435456
46
+ end
@@ -0,0 +1,28 @@
1
+ require 'rocketamf/pure/deserializer'
2
+ require 'rocketamf/pure/serializer'
3
+ require 'rocketamf/pure/remoting'
4
+
5
+ module RocketAMF
6
+ # This module holds all the modules/classes that implement AMF's functionality
7
+ # in pure ruby
8
+ module Pure
9
+ $DEBUG and warn "Using pure library for RocketAMF."
10
+ end
11
+
12
+ #:stopdoc:
13
+ # Import deserializer
14
+ Deserializer = RocketAMF::Pure::Deserializer
15
+ AMF3Deserializer = RocketAMF::Pure::AMF3Deserializer
16
+
17
+ # Import serializer
18
+ Serializer = RocketAMF::Pure::Serializer
19
+ AMF3Serializer = RocketAMF::Pure::AMF3Serializer
20
+
21
+ # Modify envelope so it can serialize/deserialize
22
+ class Envelope
23
+ remove_method :populate_from_stream
24
+ remove_method :serialize
25
+ include RocketAMF::Pure::Envelope
26
+ end
27
+ #:startdoc:
28
+ end
@@ -0,0 +1,419 @@
1
+ require 'rocketamf/pure/io_helpers'
2
+
3
+ module RocketAMF
4
+ module Pure
5
+ # Pure ruby deserializer
6
+ #--
7
+ # AMF0 deserializer, it switches over to AMF3 when it sees the switch flag
8
+ class Deserializer
9
+ def initialize
10
+ @ref_cache = []
11
+ end
12
+
13
+ def deserialize(source, type=nil)
14
+ source = StringIO.new(source) unless StringIO === source
15
+ type = read_int8 source unless type
16
+ case type
17
+ when AMF0_NUMBER_MARKER
18
+ read_number source
19
+ when AMF0_BOOLEAN_MARKER
20
+ read_boolean source
21
+ when AMF0_STRING_MARKER
22
+ read_string source
23
+ when AMF0_OBJECT_MARKER
24
+ read_object source
25
+ when AMF0_NULL_MARKER
26
+ nil
27
+ when AMF0_UNDEFINED_MARKER
28
+ nil
29
+ when AMF0_REFERENCE_MARKER
30
+ read_reference source
31
+ when AMF0_HASH_MARKER
32
+ read_hash source
33
+ when AMF0_STRICT_ARRAY_MARKER
34
+ read_array source
35
+ when AMF0_DATE_MARKER
36
+ read_date source
37
+ when AMF0_LONG_STRING_MARKER
38
+ read_string source, true
39
+ when AMF0_UNSUPPORTED_MARKER
40
+ nil
41
+ when AMF0_XML_MARKER
42
+ read_string source, true
43
+ when AMF0_TYPED_OBJECT_MARKER
44
+ read_typed_object source
45
+ when AMF0_AMF3_MARKER
46
+ AMF3Deserializer.new.deserialize(source)
47
+ else
48
+ raise AMFError, "Invalid type: #{type}"
49
+ end
50
+ end
51
+
52
+ private
53
+ include RocketAMF::Pure::ReadIOHelpers
54
+
55
+ def read_number source
56
+ res = read_double source
57
+ res.is_a?(Float)&&res.nan? ? nil : res # check for NaN and convert them to nil
58
+ end
59
+
60
+ def read_boolean source
61
+ read_int8(source) != 0
62
+ end
63
+
64
+ def read_string source, long=false
65
+ len = long ? read_word32_network(source) : read_word16_network(source)
66
+ str = source.read(len)
67
+ str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
68
+ str
69
+ end
70
+
71
+ def read_object source, add_to_ref_cache=true
72
+ obj = {}
73
+ @ref_cache << obj if add_to_ref_cache
74
+ while true
75
+ key = read_string source
76
+ type = read_int8 source
77
+ break if type == AMF0_OBJECT_END_MARKER
78
+ obj[key.to_sym] = deserialize(source, type)
79
+ end
80
+ obj
81
+ end
82
+
83
+ def read_reference source
84
+ index = read_word16_network(source)
85
+ @ref_cache[index]
86
+ end
87
+
88
+ def read_hash source
89
+ len = read_word32_network(source) # Read and ignore length
90
+
91
+ # Read first pair
92
+ key = read_string source
93
+ type = read_int8 source
94
+ return [] if type == AMF0_OBJECT_END_MARKER
95
+
96
+ # We need to figure out whether this is a real hash, or whether some stupid serializer gave up
97
+ if key.to_i.to_s == key
98
+ # Array
99
+ obj = []
100
+ @ref_cache << obj
101
+
102
+ obj[key.to_i] = deserialize(source, type)
103
+ while true
104
+ key = read_string source
105
+ type = read_int8 source
106
+ break if type == AMF0_OBJECT_END_MARKER
107
+ obj[key.to_i] = deserialize(source, type)
108
+ end
109
+ else
110
+ # Hash
111
+ obj = {}
112
+ @ref_cache << obj
113
+
114
+ obj[key.to_sym] = deserialize(source, type)
115
+ while true
116
+ key = read_string source
117
+ type = read_int8 source
118
+ break if type == AMF0_OBJECT_END_MARKER
119
+ obj[key.to_sym] = deserialize(source, type)
120
+ end
121
+ end
122
+ obj
123
+ end
124
+
125
+ def read_array source
126
+ len = read_word32_network(source)
127
+ array = []
128
+ @ref_cache << array
129
+
130
+ 0.upto(len - 1) do
131
+ array << deserialize(source)
132
+ end
133
+ array
134
+ end
135
+
136
+ def read_date source
137
+ seconds = read_double(source).to_f/1000
138
+ time = Time.at(seconds)
139
+ tz = read_word16_network(source) # Unused
140
+ time
141
+ end
142
+
143
+ def read_typed_object source
144
+ # Create object to add to ref cache
145
+ class_name = read_string source
146
+ obj = RocketAMF::ClassMapper.get_ruby_obj class_name
147
+ @ref_cache << obj
148
+
149
+ # Read object props
150
+ props = read_object source, false
151
+
152
+ # Populate object
153
+ RocketAMF::ClassMapper.populate_ruby_obj obj, props, {}
154
+ return obj
155
+ end
156
+ end
157
+
158
+ # AMF3 implementation of deserializer, loaded automatically by the AMF0
159
+ # deserializer when needed
160
+ class AMF3Deserializer
161
+ def initialize
162
+ @string_cache = []
163
+ @object_cache = []
164
+ @trait_cache = []
165
+ end
166
+
167
+ def deserialize(source, type=nil)
168
+ source = StringIO.new(source) unless StringIO === source
169
+ type = read_int8 source unless type
170
+ case type
171
+ when AMF3_UNDEFINED_MARKER
172
+ nil
173
+ when AMF3_NULL_MARKER
174
+ nil
175
+ when AMF3_FALSE_MARKER
176
+ false
177
+ when AMF3_TRUE_MARKER
178
+ true
179
+ when AMF3_INTEGER_MARKER
180
+ read_integer source
181
+ when AMF3_DOUBLE_MARKER
182
+ read_number source
183
+ when AMF3_STRING_MARKER
184
+ read_string source
185
+ when AMF3_XML_DOC_MARKER, AMF3_XML_MARKER
186
+ read_xml source
187
+ when AMF3_DATE_MARKER
188
+ read_date source
189
+ when AMF3_ARRAY_MARKER
190
+ read_array source
191
+ when AMF3_OBJECT_MARKER
192
+ read_object source
193
+ when AMF3_BYTE_ARRAY_MARKER
194
+ read_amf3_byte_array source
195
+ when AMF3_DICT_MARKER
196
+ read_dict source
197
+ else
198
+ raise AMFError, "Invalid type: #{type}"
199
+ end
200
+ end
201
+
202
+ private
203
+ include RocketAMF::Pure::ReadIOHelpers
204
+
205
+ def read_integer source
206
+ n = 0
207
+ b = read_word8(source) || 0
208
+ result = 0
209
+
210
+ while ((b & 0x80) != 0 && n < 3)
211
+ result = result << 7
212
+ result = result | (b & 0x7f)
213
+ b = read_word8(source) || 0
214
+ n = n + 1
215
+ end
216
+
217
+ if (n < 3)
218
+ result = result << 7
219
+ result = result | b
220
+ else
221
+ #Use all 8 bits from the 4th byte
222
+ result = result << 8
223
+ result = result | b
224
+
225
+ #Check if the integer should be negative
226
+ if (result > MAX_INTEGER)
227
+ result -= (1 << 29)
228
+ end
229
+ end
230
+ result
231
+ end
232
+
233
+ def read_number source
234
+ res = read_double source
235
+ res.is_a?(Float)&&res.nan? ? nil : res # check for NaN and convert them to nil
236
+ end
237
+
238
+ def read_string source
239
+ type = read_integer source
240
+ isReference = (type & 0x01) == 0
241
+
242
+ if isReference
243
+ reference = type >> 1
244
+ return @string_cache[reference]
245
+ else
246
+ length = type >> 1
247
+ str = ""
248
+ if length > 0
249
+ str = source.read(length)
250
+ str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
251
+ @string_cache << str
252
+ end
253
+ return str
254
+ end
255
+ end
256
+
257
+ def read_xml source
258
+ type = read_integer source
259
+ isReference = (type & 0x01) == 0
260
+
261
+ if isReference
262
+ reference = type >> 1
263
+ return @object_cache[reference]
264
+ else
265
+ length = type >> 1
266
+ str = ""
267
+ if length > 0
268
+ str = source.read(length)
269
+ str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
270
+ @object_cache << str
271
+ end
272
+ return str
273
+ end
274
+ end
275
+
276
+ def read_amf3_byte_array source
277
+ type = read_integer source
278
+ isReference = (type & 0x01) == 0
279
+
280
+ if isReference
281
+ reference = type >> 1
282
+ return @object_cache[reference]
283
+ else
284
+ length = type >> 1
285
+ obj = StringIO.new source.read(length)
286
+ @object_cache << obj
287
+ obj
288
+ end
289
+ end
290
+
291
+ def read_array source
292
+ type = read_integer source
293
+ isReference = (type & 0x01) == 0
294
+
295
+ if isReference
296
+ reference = type >> 1
297
+ return @object_cache[reference]
298
+ else
299
+ length = type >> 1
300
+ propertyName = read_string source
301
+ if propertyName != ""
302
+ array = {}
303
+ @object_cache << array
304
+ begin
305
+ while(propertyName.length)
306
+ value = deserialize(source)
307
+ array[propertyName] = value
308
+ propertyName = read_string source
309
+ end
310
+ rescue Exception => e #end of object exception, because propertyName.length will be non existent
311
+ end
312
+ 0.upto(length - 1) do |i|
313
+ array["" + i.to_s] = deserialize(source)
314
+ end
315
+ else
316
+ array = []
317
+ @object_cache << array
318
+ 0.upto(length - 1) do
319
+ array << deserialize(source)
320
+ end
321
+ end
322
+ array
323
+ end
324
+ end
325
+
326
+ def read_object source
327
+ type = read_integer source
328
+ isReference = (type & 0x01) == 0
329
+
330
+ if isReference
331
+ reference = type >> 1
332
+ return @object_cache[reference]
333
+ else
334
+ class_type = type >> 1
335
+ class_is_reference = (class_type & 0x01) == 0
336
+
337
+ if class_is_reference
338
+ reference = class_type >> 1
339
+ traits = @trait_cache[reference]
340
+ else
341
+ externalizable = (class_type & 0x02) != 0
342
+ dynamic = (class_type & 0x04) != 0
343
+ attribute_count = class_type >> 3
344
+ class_name = read_string source
345
+
346
+ class_attributes = []
347
+ attribute_count.times{class_attributes << read_string(source)} # Read class members
348
+
349
+ traits = {
350
+ :class_name => class_name,
351
+ :members => class_attributes,
352
+ :externalizable => externalizable,
353
+ :dynamic => dynamic
354
+ }
355
+ @trait_cache << traits
356
+ end
357
+
358
+ obj = RocketAMF::ClassMapper.get_ruby_obj traits[:class_name]
359
+ @object_cache << obj
360
+
361
+ if traits[:externalizable]
362
+ obj.externalized_data = deserialize(source)
363
+ else
364
+ props = {}
365
+ traits[:members].each do |key|
366
+ value = deserialize(source)
367
+ props[key.to_sym] = value
368
+ end
369
+
370
+ dynamic_props = nil
371
+ if traits[:dynamic]
372
+ dynamic_props = {}
373
+ while (key = read_string source) && key.length != 0 do # read next key
374
+ value = deserialize(source)
375
+ dynamic_props[key.to_sym] = value
376
+ end
377
+ end
378
+
379
+ RocketAMF::ClassMapper.populate_ruby_obj obj, props, dynamic_props
380
+ end
381
+ obj
382
+ end
383
+ end
384
+
385
+ def read_date source
386
+ type = read_integer source
387
+ isReference = (type & 0x01) == 0
388
+ if isReference
389
+ reference = type >> 1
390
+ return @object_cache[reference]
391
+ else
392
+ seconds = read_double(source).to_f/1000
393
+ time = Time.at(seconds)
394
+ @object_cache << time
395
+ time
396
+ end
397
+ end
398
+
399
+ def read_dict source
400
+ type = read_integer source
401
+ # Currently duplicate dictionaries send false, but I'll leave this in here just in case
402
+ isReference = (type & 0x01) == 0
403
+ if isReference
404
+ reference = type >> 1
405
+ return @object_cache[reference]
406
+ else
407
+ dict = {}
408
+ @object_cache << dict
409
+ length = type >> 1
410
+ skip = read_integer source # TODO: Handle when specs are updated
411
+ 0.upto(length - 1) do |i|
412
+ dict[deserialize(source)] = deserialize(source)
413
+ end
414
+ dict
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end