scotttam-RocketAMF 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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