Class: RubySol::Pure::Deserializer

Inherits:
Object
  • Object
show all
Includes:
ReadIOHelpers
Defined in:
lib/ruby_sol/pure/deserializer.rb

Overview

Pure ruby deserializer for AMF0 and AMF3

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ReadIOHelpers

#byte_order, #byte_order_little?, #read_double, #read_int16_network, #read_int8, #read_word16_network, #read_word32_network, #read_word8

Constructor Details

#initialize(class_mapper) ⇒ Deserializer

Pass in the class mapper instance to use when deserializing. This enables better caching behavior in the class mapper and allows one to change mappings between deserialization attempts.



12
13
14
15
# File 'lib/ruby_sol/pure/deserializer.rb', line 12

def initialize class_mapper
  @class_mapper = class_mapper
  reset_caches()
end

Instance Attribute Details

#sourceObject

Returns the value of attribute source.



7
8
9
# File 'lib/ruby_sol/pure/deserializer.rb', line 7

def source
  @source
end

Instance Method Details

#amf0_deserialize(type = nil) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/ruby_sol/pure/deserializer.rb', line 61

def amf0_deserialize type=nil
  type = read_int8 @source unless type
  case type
  when AMF0_NUMBER_MARKER
    amf0_read_number
  when AMF0_BOOLEAN_MARKER
    amf0_read_boolean
  when AMF0_STRING_MARKER
    amf0_read_string
  when AMF0_OBJECT_MARKER
    amf0_read_object
  when AMF0_NULL_MARKER
    nil
  when AMF0_UNDEFINED_MARKER
    nil
  when AMF0_REFERENCE_MARKER
    amf0_read_reference
  when AMF0_HASH_MARKER
    amf0_read_hash
  when AMF0_STRICT_ARRAY_MARKER
    amf0_read_array
  when AMF0_DATE_MARKER
    amf0_read_date
  when AMF0_LONG_STRING_MARKER
    amf0_read_string true
  when AMF0_UNSUPPORTED_MARKER
    nil
  when AMF0_XML_MARKER
    amf0_read_string true
  when AMF0_TYPED_OBJECT_MARKER
    amf0_read_typed_object
  when AMF0_AMF3_MARKER
    deserialize(3, nil)
  else
    raise AMFError, "Invalid type: #{type}"
  end
end

#amf0_read_arrayObject



120
121
122
123
124
125
126
127
128
129
# File 'lib/ruby_sol/pure/deserializer.rb', line 120

def amf0_read_array
  len = read_word32_network(@source)
  array = []
  @ref_cache << array

  0.upto(len - 1) do
    array << amf0_deserialize
  end
  array
end

#amf0_read_booleanObject



104
105
106
# File 'lib/ruby_sol/pure/deserializer.rb', line 104

def amf0_read_boolean
  read_int8(@source) != 0
end

#amf0_read_dateObject



131
132
133
134
135
136
# File 'lib/ruby_sol/pure/deserializer.rb', line 131

def amf0_read_date
  seconds = read_double(@source).to_f/1000
  time = Time.at(seconds)
  tz = read_word16_network(@source) # Unused
  time
end

#amf0_read_hashObject



148
149
150
151
152
153
# File 'lib/ruby_sol/pure/deserializer.rb', line 148

def amf0_read_hash
  len = read_word32_network(@source) # Read and ignore length
  obj = {}
  @ref_cache << obj
  amf0_read_props obj
end

#amf0_read_numberObject



99
100
101
102
# File 'lib/ruby_sol/pure/deserializer.rb', line 99

def amf0_read_number
  res = read_double @source
  (res.is_a?(Float) && res.nan?) ? nil : res # check for NaN and convert them to nil
end

#amf0_read_object(add_to_ref_cache = true) ⇒ Object



155
156
157
158
159
160
161
162
163
164
# File 'lib/ruby_sol/pure/deserializer.rb', line 155

def amf0_read_object add_to_ref_cache=true
  # Create "object" and add to ref cache (it's always a Hash)
  obj = @class_mapper.get_ruby_obj ""
  @ref_cache << obj

  # Populate object
  props = amf0_read_props
  @class_mapper.populate_ruby_obj obj, props
  return obj
end

#amf0_read_props(obj = {}) ⇒ Object



138
139
140
141
142
143
144
145
146
# File 'lib/ruby_sol/pure/deserializer.rb', line 138

def amf0_read_props obj={}
  while true
    key = amf0_read_string
    type = read_int8 @source
    break if type == AMF0_OBJECT_END_MARKER
    obj[key] = amf0_deserialize(type)
  end
  obj
end

#amf0_read_referenceObject



115
116
117
118
# File 'lib/ruby_sol/pure/deserializer.rb', line 115

def amf0_read_reference
  index = read_word16_network(@source)
  @ref_cache[index]
end

#amf0_read_string(long = false) ⇒ Object



108
109
110
111
112
113
# File 'lib/ruby_sol/pure/deserializer.rb', line 108

def amf0_read_string long=false
  len = long ? read_word32_network(@source) : read_word16_network(@source)
  str = @source.read(len)
  str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
  str
end

#amf0_read_typed_objectObject



166
167
168
169
170
171
172
173
174
175
176
# File 'lib/ruby_sol/pure/deserializer.rb', line 166

def amf0_read_typed_object
  # Create object to add to ref cache
  class_name = amf0_read_string
  obj = @class_mapper.get_ruby_obj class_name
  @ref_cache << obj

  # Populate object
  props = amf0_read_props
  @class_mapper.populate_ruby_obj obj, props
  return obj
end

#amf3_deserializeObject



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/ruby_sol/pure/deserializer.rb', line 178

def amf3_deserialize
  type = read_int8 @source
  case type
  when AMF3_UNDEFINED_MARKER
    nil
  when AMF3_NULL_MARKER
    nil
  when AMF3_FALSE_MARKER
    false
  when AMF3_TRUE_MARKER
    true
  when AMF3_INTEGER_MARKER
    amf3_read_integer
  when AMF3_DOUBLE_MARKER
    amf3_read_number
  when AMF3_STRING_MARKER
    amf3_read_string
  when AMF3_XML_DOC_MARKER, AMF3_XML_MARKER
    amf3_read_xml
  when AMF3_DATE_MARKER
    amf3_read_date
  when AMF3_ARRAY_MARKER
    amf3_read_array
  when AMF3_OBJECT_MARKER
    amf3_read_object
  when AMF3_BYTE_ARRAY_MARKER
    amf3_read_byte_array
  when AMF3_VECTOR_INT_MARKER, AMF3_VECTOR_UINT_MARKER, AMF3_VECTOR_DOUBLE_MARKER, AMF3_VECTOR_OBJECT_MARKER
    amf3_read_vector type
  when AMF3_DICT_MARKER
    amf3_read_dict
  else
    raise AMFError, "Invalid type: #{type}"
  end
end

#amf3_read_arrayObject



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/ruby_sol/pure/deserializer.rb', line 300

def amf3_read_array
  type = amf3_read_integer
  is_reference = (type & 0x01) == 0

  if is_reference
    reference = type >> 1
    return @object_cache[reference]
  else
    length = type >> 1
    property_name = amf3_read_string
    array = property_name.length > 0 ? {} : []
    @object_cache << array

    while property_name.length > 0
      value = amf3_deserialize
      array[property_name] = value
      property_name = amf3_read_string
    end
    0.upto(length - 1) {|i| array[i] = amf3_deserialize }

    array
  end
end

#amf3_read_byte_arrayObject



285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/ruby_sol/pure/deserializer.rb', line 285

def amf3_read_byte_array
  type = amf3_read_integer
  is_reference = (type & 0x01) == 0

  if is_reference
    reference = type >> 1
    return @object_cache[reference]
  else
    length = type >> 1
    obj = StringIO.new @source.read(length)
    @object_cache << obj
    obj
  end
end

#amf3_read_dateObject



390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/ruby_sol/pure/deserializer.rb', line 390

def amf3_read_date
  type = amf3_read_integer
  is_reference = (type & 0x01) == 0
  if is_reference
    reference = type >> 1
    return @object_cache[reference]
  else
    seconds = read_double(@source).to_f/1000
    time = Time.at(seconds)
    @object_cache << time
    time
  end
end

#amf3_read_dictObject



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/ruby_sol/pure/deserializer.rb', line 404

def amf3_read_dict
  type = amf3_read_integer
  is_reference = (type & 0x01) == 0
  if is_reference
    reference = type >> 1
    return @object_cache[reference]
  else
    dict = {}
    @object_cache << dict
    length = type >> 1
    weak_keys = read_int8 @source # Ignore: Not supported in ruby
    0.upto(length - 1) do |i|
      dict[amf3_deserialize] = amf3_deserialize
    end
    dict
  end
end

#amf3_read_integerObject



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/ruby_sol/pure/deserializer.rb', line 214

def amf3_read_integer
  n = 0
  b = read_word8(@source) || 0
  result = 0

  while ((b & 0x80) != 0 && n < 3)
    result = result << 7
    result = result | (b & 0x7f)
    b = read_word8(@source) || 0
    n = n + 1
  end

  if (n < 3)
    result = result << 7
    result = result | b
  else
    #Use all 8 bits from the 4th byte
    result = result << 8
    result = result | b

    #Check if the integer should be negative
    if (result > MAX_INTEGER)
      result -= (1 << 29)
    end
  end
  result
end

#amf3_read_numberObject



242
243
244
245
# File 'lib/ruby_sol/pure/deserializer.rb', line 242

def amf3_read_number
  res = read_double @source
  (res.is_a?(Float) && res.nan?) ? nil : res # check for NaN and convert them to nil
end

#amf3_read_objectObject



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/ruby_sol/pure/deserializer.rb', line 324

def amf3_read_object
  type = amf3_read_integer
  is_reference = (type & 0x01) == 0

  if is_reference
    reference = type >> 1
    return @object_cache[reference]
  else
    class_type = type >> 1
    class_is_reference = (class_type & 0x01) == 0

    if class_is_reference
      reference = class_type >> 1
      traits = @trait_cache[reference]
    else
      externalizable = (class_type & 0x02) != 0
      dynamic = (class_type & 0x04) != 0
      attribute_count = class_type >> 3
      class_name = amf3_read_string

      class_attributes = []
      attribute_count.times{class_attributes << amf3_read_string} # Read class members

      traits = {
                :class_name => class_name,
                :members => class_attributes,
                :externalizable => externalizable,
                :dynamic => dynamic
               }
      @trait_cache << traits
    end

    # Optimization for deserializing ArrayCollection
    if traits[:class_name] == "flex.messaging.io.ArrayCollection"
      arr = amf3_deserialize # Adds ArrayCollection array to object cache
      @object_cache << arr # Add again for ArrayCollection source array
      return arr
    end

    obj = @class_mapper.get_ruby_obj traits[:class_name]
    @object_cache << obj

    if traits[:externalizable]
      obj.read_external self
    else
      props = {}
      traits[:members].each do |key|
        value = amf3_deserialize
        props[key] = value
      end

      dynamic_props = nil
      if traits[:dynamic]
        dynamic_props = {}
        while (key = amf3_read_string) && key.length != 0  do # read next key
          value = amf3_deserialize
          dynamic_props[key] = value
        end
      end

      @class_mapper.populate_ruby_obj obj, props, dynamic_props
    end
    obj
  end
end

#amf3_read_stringObject



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/ruby_sol/pure/deserializer.rb', line 247

def amf3_read_string
  type = amf3_read_integer
  is_reference = (type & 0x01) == 0

  if is_reference
    reference = type >> 1
    return @string_cache[reference]
  else
    length = type >> 1
    str = ""
    if length > 0
      str = @source.read(length)
      str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
      @string_cache << str
    end
    return str
  end
end

#amf3_read_vector(vector_type) ⇒ Object



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# File 'lib/ruby_sol/pure/deserializer.rb', line 422

def amf3_read_vector vector_type
  type = amf3_read_integer
  is_reference = (type & 0x01) == 0
  if is_reference
    reference = type >> 1
    return @object_cache[reference]
  else
    vec = []
    @object_cache << vec
    length = type >> 1
    fixed_vector = read_int8 @source # Ignore
    case vector_type
    when AMF3_VECTOR_INT_MARKER
      0.upto(length - 1) do |i|
        int = read_word32_network(@source)
        int = int - 2**32 if int > MAX_INTEGER
        vec << int
      end
    when AMF3_VECTOR_UINT_MARKER
      0.upto(length - 1) do |i|
        vec << read_word32_network(@source)
   #     puts vec[i].to_s(2)
      end
    when AMF3_VECTOR_DOUBLE_MARKER
      0.upto(length - 1) do |i|
        vec << amf3_read_number
      end
    when AMF3_VECTOR_OBJECT_MARKER
      vector_class = amf3_read_string # Ignore
  #    puts vector_class
      0.upto(length - 1) do |i|
        vec << amf3_deserialize
      end
    end
    vec
  end
end

#amf3_read_xmlObject



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/ruby_sol/pure/deserializer.rb', line 266

def amf3_read_xml
  type = amf3_read_integer
  is_reference = (type & 0x01) == 0

  if is_reference
    reference = type >> 1
    return @object_cache[reference]
  else
    length = type >> 1
    str = ""
    if length > 0
      str = @source.read(length)
      str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
      @object_cache << str
    end
    return str
  end
end

#deserialize(version, source) ⇒ Object

Deserialize the source using AMF0 or AMF3. Source should either be a string or StringIO object. If you pass a StringIO object, it will have its position updated to the end of the deserialized data.

Raises:

  • (ArgumentError)


28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/ruby_sol/pure/deserializer.rb', line 28

def deserialize version, source
  raise ArgumentError, "unsupported version #{version}" unless [0,3].include?(version)
  @version = version

  if StringIO === source
    @source = source
  elsif source
    @source = StringIO.new(source)
  elsif @source.nil?
    raise AMFError, "no source to deserialize"
  end

  reset_caches()

  if @version == 0
    return amf0_deserialize
  else
    return amf3_deserialize
  end
end

#read_objectObject

Reads an object from the deserializer’s stream and returns it.



50
51
52
53
54
55
56
# File 'lib/ruby_sol/pure/deserializer.rb', line 50

def read_object
  if @version == 0
    return amf0_deserialize
  else
    return amf3_deserialize
  end
end

#reset_cachesObject



17
18
19
20
21
22
# File 'lib/ruby_sol/pure/deserializer.rb', line 17

def reset_caches
  @ref_cache = []
  @string_cache = []
  @object_cache = []
  @trait_cache = []
end