class RubySol::Pure::Deserializer

Pure ruby deserializer for AMF0 and AMF3

Attributes

source[RW]

Public Class Methods

new(class_mapper) click to toggle source

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.

   # File lib/ruby_sol/pure/deserializer.rb
12 def initialize class_mapper
13   @class_mapper = class_mapper
14   reset_caches()
15 end

Public Instance Methods

amf0_deserialize(type=nil) click to toggle source
   # File lib/ruby_sol/pure/deserializer.rb
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
amf0_read_array() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf0_read_boolean() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
104 def amf0_read_boolean
105   read_int8(@source) != 0
106 end
amf0_read_date() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf0_read_hash() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf0_read_number() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
 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
amf0_read_object(add_to_ref_cache=true) click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf0_read_props(obj={}) click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf0_read_reference() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
115 def amf0_read_reference
116   index = read_word16_network(@source)
117   @ref_cache[index]
118 end
amf0_read_string(long=false) click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf0_read_typed_object() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_deserialize() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_array() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_byte_array() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_date() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_dict() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_integer() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_number() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_object() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_string() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_vector(vector_type) click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
amf3_read_xml() click to toggle source
    # File lib/ruby_sol/pure/deserializer.rb
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
deserialize(version, source) click to toggle source

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.

   # File lib/ruby_sol/pure/deserializer.rb
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
read_object() click to toggle source

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

   # File lib/ruby_sol/pure/deserializer.rb
50 def read_object
51   if @version == 0
52     return amf0_deserialize
53   else
54     return amf3_deserialize
55   end
56 end
reset_caches() click to toggle source
   # File lib/ruby_sol/pure/deserializer.rb
17 def reset_caches
18   @ref_cache = []
19   @string_cache = []
20   @object_cache = []
21   @trait_cache = []
22 end