class RubySol::Pure::Serializer

Pure ruby serializer for AMF0 and AMF3

Attributes

stream[R]
version[R]

Public Class Methods

new(class_mapper) click to toggle source

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

   # File lib/ruby_sol/pure/serializer.rb
12 def initialize class_mapper
13   @class_mapper = class_mapper
14   @stream = ""
15   @depth = 0
16   reset_caches()
17 end

Public Instance Methods

amf0_serialize(obj) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
 90 def amf0_serialize obj
 91   if @ref_cache[obj] != nil
 92     amf0_write_reference @ref_cache[obj]
 93   elsif obj.respond_to?(:encode_amf)
 94     obj.encode_amf(self)
 95   elsif obj.is_a?(NilClass)
 96     amf0_write_null
 97   elsif obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
 98     amf0_write_boolean obj
 99   elsif obj.is_a?(Numeric)
100     amf0_write_number obj
101   elsif obj.is_a?(Symbol) || obj.is_a?(String)
102     amf0_write_string obj.to_s
103   elsif obj.is_a?(Time)
104     amf0_write_time obj
105   elsif obj.is_a?(Date)
106     amf0_write_date obj
107   elsif obj.is_a?(Array)
108     amf0_write_array obj
109   elsif obj.is_a?(Hash) ||obj.is_a?(Object)
110     amf0_write_object obj
111   end
112 end
amf0_write_array(array) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
170 def amf0_write_array array
171   @ref_cache.add_obj array
172   @stream << AMF0_STRICT_ARRAY_MARKER
173   @stream << pack_word32_network(array.length)
174   array.each do |elem|
175     amf0_serialize elem
176   end
177 end
amf0_write_boolean(bool) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
118 def amf0_write_boolean bool
119   @stream << AMF0_BOOLEAN_MARKER
120   @stream << pack_int8(bool ? 1 : 0)
121 end
amf0_write_date(date) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
159 def amf0_write_date date
160   @stream << AMF0_DATE_MARKER
161   @stream << pack_double(date.strftime("%Q").to_i)
162   @stream << pack_int16_network(0) # Time zone
163 end
amf0_write_null() click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
114 def amf0_write_null
115   @stream << AMF0_NULL_MARKER
116 end
amf0_write_number(num) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
123 def amf0_write_number num
124   @stream << AMF0_NUMBER_MARKER
125   @stream << pack_double(num)
126 end
amf0_write_object(obj, props=nil) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
179 def amf0_write_object obj, props=nil
180   @ref_cache.add_obj obj
181 
182   props = @class_mapper.props_for_serialization obj if props.nil?
183 
184   # Is it a typed object?
185   class_name = @class_mapper.get_as_class_name obj
186   if class_name
187     class_name = class_name.encode("UTF-8").force_encoding("ASCII-8BIT") if class_name.respond_to?(:encode)
188     @stream << AMF0_TYPED_OBJECT_MARKER
189     @stream << pack_int16_network(class_name.bytesize)
190     @stream << class_name
191   else
192     @stream << AMF0_OBJECT_MARKER
193   end
194 
195   # Write prop list
196   props.sort.each do |key, value| # Sort keys before writing
197     key = key.encode("UTF-8").force_encoding("ASCII-8BIT") if key.respond_to?(:encode)
198     @stream << pack_int16_network(key.bytesize)
199     @stream << key
200     amf0_serialize value
201   end
202 
203   # Write end
204   @stream << pack_int16_network(0)
205   @stream << AMF0_OBJECT_END_MARKER
206 end
amf0_write_reference(index) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
165 def amf0_write_reference index
166   @stream << AMF0_REFERENCE_MARKER
167   @stream << pack_int16_network(index)
168 end
amf0_write_string(str) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
128 def amf0_write_string str
129   str = str.encode("UTF-8").force_encoding("ASCII-8BIT") if str.respond_to?(:encode)
130   len = str.bytesize
131   if len > 2**16-1
132     @stream << AMF0_LONG_STRING_MARKER
133     @stream << pack_word32_network(len)
134   else
135     @stream << AMF0_STRING_MARKER
136     @stream << pack_int16_network(len)
137   end
138   @stream << str
139 end
amf0_write_string_wo_marker(str) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
141 def amf0_write_string_wo_marker str
142   str = str.encode("UTF-8").force_encoding("ASCII-8BIT") if str.respond_to?(:encode)
143   len = str.bytesize
144   throw SOLError, 'too long string' if len > 2**16-1
145   @stream << pack_int16_network(len)
146   @stream << str
147 end
amf0_write_time(time) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
149 def amf0_write_time time
150   @stream << AMF0_DATE_MARKER
151 
152   time = time.getutc # Dup and convert to UTC
153   milli = (time.to_f * 1000).to_i
154   @stream << pack_double(milli)
155 
156   @stream << pack_int16_network(0) # Time zone
157 end
amf3_serialize(obj) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
208 def amf3_serialize obj
209   if obj.respond_to?(:encode_amf)
210     obj.encode_amf(self)
211   elsif obj.is_a?(NilClass)
212     amf3_write_null
213   elsif obj.is_a?(TrueClass)
214     amf3_write_true
215   elsif obj.is_a?(FalseClass)
216     amf3_write_false
217   elsif obj.is_a?(Numeric)
218     amf3_write_numeric obj
219   elsif obj.is_a?(Symbol) || obj.is_a?(String)
220     amf3_write_string obj.to_s
221   elsif obj.is_a?(Time)
222     amf3_write_time obj
223   elsif obj.is_a?(Date)
224     amf3_write_date obj
225   elsif obj.is_a?(StringIO)
226     amf3_write_byte_array obj
227   elsif obj.is_a?(Array)
228     amf3_write_array obj
229   elsif obj.is_a?(Hash) || obj.is_a?(Object)
230     amf3_write_object obj
231   end
232 end
amf3_write_array(array) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
308 def amf3_write_array array
309   # Is it an array collection?
310   is_ac = false
311   if array.respond_to?(:is_array_collection?)
312     is_ac = array.is_array_collection?
313   else
314     is_ac = @class_mapper.use_array_collection
315   end
316 
317   # Write type marker
318   @stream << (is_ac ? AMF3_OBJECT_MARKER : AMF3_ARRAY_MARKER)
319 
320   # Write reference or cache array
321   if @object_cache[array] != nil
322     amf3_write_reference @object_cache[array]
323     return
324   else
325     @object_cache.add_obj array
326     @object_cache.add_obj nil if is_ac # The array collection source array
327   end
328 
329   # Write out traits and array marker if it's an array collection
330   if is_ac
331     class_name = "flex.messaging.io.ArrayCollection"
332     if @trait_cache[class_name] != nil
333       @stream << pack_integer(@trait_cache[class_name] << 2 | 0x01)
334     else
335       @trait_cache.add_obj class_name
336       @stream << "\a" # Externalizable, non-dynamic
337       amf3_write_utf8_vr(class_name)
338     end
339     @stream << AMF3_ARRAY_MARKER
340   end
341 
342   # Build AMF string for array
343   header = array.length << 1 # make room for a low bit of 1
344   header = header | 1 # set the low bit to 1
345   @stream << pack_integer(header)
346   @stream << AMF3_CLOSE_DYNAMIC_ARRAY
347   array.each do |elem|
348     amf3_serialize elem
349   end
350 end
amf3_write_byte_array(array) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
296 def amf3_write_byte_array array
297   @stream << AMF3_BYTE_ARRAY_MARKER
298   if @object_cache[array] != nil
299     amf3_write_reference @object_cache[array]
300   else
301     @object_cache.add_obj array
302     str = array.string
303     @stream << pack_integer(str.bytesize << 1 | 1)
304     @stream << str
305   end
306 end
amf3_write_date(date) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
282 def amf3_write_date date
283   @stream << AMF3_DATE_MARKER
284   if @object_cache[date] != nil
285     amf3_write_reference @object_cache[date]
286   else
287     # Cache date
288     @object_cache.add_obj date
289 
290     # Build AMF string
291     @stream << AMF3_NULL_MARKER
292     @stream << pack_double(date.strftime("%Q").to_i)
293   end
294 end
amf3_write_false() click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
247 def amf3_write_false
248   @stream << AMF3_FALSE_MARKER
249 end
amf3_write_null() click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
239 def amf3_write_null
240   @stream << AMF3_NULL_MARKER
241 end
amf3_write_numeric(num) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
251 def amf3_write_numeric num
252   if !num.integer? || num < MIN_INTEGER || num > MAX_INTEGER # Check valid range for 29 bits
253     @stream << AMF3_DOUBLE_MARKER
254     @stream << pack_double(num)
255   else
256     @stream << AMF3_INTEGER_MARKER
257     @stream << pack_integer(num)
258   end
259 end
amf3_write_object(obj, props=nil, traits=nil) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
352 def amf3_write_object obj, props=nil, traits=nil
353   @stream << AMF3_OBJECT_MARKER
354 
355   # Caching...
356   if @object_cache[obj] != nil
357     amf3_write_reference @object_cache[obj]
358     return
359   end
360   @object_cache.add_obj obj
361 
362   # Calculate traits if not given
363   is_default = false
364   if traits.nil?
365     traits = {
366               :class_name => @class_mapper.get_as_class_name(obj),
367               :members => [],
368               :externalizable => false,
369               :dynamic => true
370              }
371     is_default = true unless traits[:class_name]
372   end
373   class_name = is_default ? "__default__" : traits[:class_name]
374 
375   # Write out traits
376   if (class_name && @trait_cache[class_name] != nil)
377     @stream << pack_integer(@trait_cache[class_name] << 2 | 0x01)
378   else
379     @trait_cache.add_obj class_name if class_name
380 
381     # Write out trait header
382     header = 0x03 # Not object ref and not trait ref
383     header |= 0x02 << 2 if traits[:dynamic]
384     header |= 0x01 << 2 if traits[:externalizable]
385     header |= traits[:members].length << 4
386     @stream << pack_integer(header)
387 
388     # Write out class name
389     if class_name == "__default__"
390       amf3_write_utf8_vr("")
391     else
392       amf3_write_utf8_vr(class_name.to_s)
393     end
394 
395     # Write out members
396     traits[:members].each {|m| amf3_write_utf8_vr(m)}
397   end
398 
399   # If externalizable, take externalized data shortcut
400   if traits[:externalizable]
401     obj.write_external(self)
402     return
403   end
404 
405   # Extract properties if not given
406   props = @class_mapper.props_for_serialization(obj) if props.nil?
407 
408   # Write out sealed properties
409   traits[:members].each do |m|
410     amf3_serialize props[m]
411     props.delete(m)
412   end
413 
414   # Write out dynamic properties
415   if traits[:dynamic]
416     # Write out dynamic properties
417     props.sort.each do |key, val| # Sort props until Ruby 1.9 becomes common
418       amf3_write_utf8_vr key.to_s
419       amf3_serialize val
420     end
421 
422     # Write close
423     @stream << AMF3_CLOSE_DYNAMIC_OBJECT
424   end
425 end
amf3_write_reference(index) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
234 def amf3_write_reference index
235   header = index << 1 # shift value left to leave a low bit of 0
236   @stream << pack_integer(header)
237 end
amf3_write_string(str) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
261 def amf3_write_string str
262   @stream << AMF3_STRING_MARKER
263   amf3_write_utf8_vr str
264 end
amf3_write_time(time) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
266 def amf3_write_time time
267   @stream << AMF3_DATE_MARKER
268   if @object_cache[time] != nil
269     amf3_write_reference @object_cache[time]
270   else
271     # Cache time
272     @object_cache.add_obj time
273 
274     # Build AMF string
275     time = time.getutc # Dup and convert to UTC
276     milli = (time.to_f * 1000).to_i
277     @stream << AMF3_NULL_MARKER
278     @stream << pack_double(milli)
279   end
280 end
amf3_write_true() click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
243 def amf3_write_true
244   @stream << AMF3_TRUE_MARKER
245 end
amf3_write_utf8_vr(str, encode=true) click to toggle source
    # File lib/ruby_sol/pure/serializer.rb
427 def amf3_write_utf8_vr str, encode=true
428   if str.respond_to?(:encode)
429     if encode
430       str = str.encode("UTF-8")
431     else
432       str = str.dup if str.frozen?
433     end
434     str.force_encoding("ASCII-8BIT")
435   end
436 
437   if str == ''
438     @stream << AMF3_EMPTY_STRING
439   elsif @string_cache[str] != nil
440     amf3_write_reference @string_cache[str]
441   else
442     # Cache string
443     @string_cache.add_obj str
444 
445     # Build AMF string
446     @stream << pack_integer(str.bytesize << 1 | 1)
447     @stream << str
448   end
449 end
reset_caches() click to toggle source
   # File lib/ruby_sol/pure/serializer.rb
19 def reset_caches
20   @ref_cache = SerializerCache.new :object
21   @string_cache = SerializerCache.new :string
22   @object_cache = SerializerCache.new :object
23   @trait_cache = SerializerCache.new :string
24 end
serialize(version, obj) click to toggle source

Serialize the given object using AMF0 or AMF3. Can be called from inside encode_amf, but make sure to pass in the proper version or it may not be possible to decode. Use the serializer version attribute for this.

   # File lib/ruby_sol/pure/serializer.rb
29 def serialize version, obj
30   raise ArgumentError, "unsupported version #{version}" unless [0,3].include?(version)
31   @version = version
32 
33   # Initialize caches
34   if @depth == 0
35     if @version == 0
36       @ref_cache = SerializerCache.new :object
37     else
38       @string_cache = SerializerCache.new :string
39       @object_cache = SerializerCache.new :object
40       @trait_cache = SerializerCache.new :string
41     end
42   end
43   @depth += 1
44 
45   # Perform serialization
46   if @version == 0
47     amf0_serialize(obj)
48   else
49     amf3_serialize(obj)
50   end
51 
52   # Cleanup
53   @depth -= 1
54   if @depth == 0
55     @ref_cache = nil
56     @string_cache = nil
57     @object_cache = nil
58     @trait_cache = nil
59   end
60 
61   return @stream
62 end
write_array(arr) click to toggle source

Helper for writing arrays inside encode_amf. It uses the current AMF version to write the array.

   # File lib/ruby_sol/pure/serializer.rb
66 def write_array arr
67   if @version == 0
68     amf0_write_array arr
69   else
70     amf3_write_array arr
71   end
72 end
write_object(obj, props=nil, traits=nil) click to toggle source

Helper for writing objects inside encode_amf. It uses the current AMF version to write the object. If you pass in a property hash, it will use it rather than having the class mapper determine properties. For AMF3, you can also specify a traits hash, which can be used to reduce serialized data size or serialize things as externalizable.

   # File lib/ruby_sol/pure/serializer.rb
79 def write_object obj, props=nil, traits=nil
80   if @version == 0
81     amf0_write_object obj, props
82   else
83     amf3_write_object obj, props, traits
84   end
85 end