class RubySol::Pure::Serializer
Pure
ruby serializer for AMF0 and AMF3
Attributes
Public Class Methods
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
# 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
# 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
# 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
# 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
# File lib/ruby_sol/pure/serializer.rb 114 def amf0_write_null 115 @stream << AMF0_NULL_MARKER 116 end
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# File lib/ruby_sol/pure/serializer.rb 247 def amf3_write_false 248 @stream << AMF3_FALSE_MARKER 249 end
# File lib/ruby_sol/pure/serializer.rb 239 def amf3_write_null 240 @stream << AMF3_NULL_MARKER 241 end
# 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
# 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
# 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
# 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
# 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
# File lib/ruby_sol/pure/serializer.rb 243 def amf3_write_true 244 @stream << AMF3_TRUE_MARKER 245 end
# 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
# 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 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
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
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