RocketAMF 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/README.rdoc +36 -0
  2. data/Rakefile +54 -0
  3. data/lib/rocketamf.rb +12 -0
  4. data/lib/rocketamf/class_mapping.rb +211 -0
  5. data/lib/rocketamf/common.rb +35 -0
  6. data/lib/rocketamf/constants.rb +47 -0
  7. data/lib/rocketamf/pure.rb +30 -0
  8. data/lib/rocketamf/pure/deserializer.rb +361 -0
  9. data/lib/rocketamf/pure/io_helpers.rb +94 -0
  10. data/lib/rocketamf/pure/remoting.rb +89 -0
  11. data/lib/rocketamf/pure/serializer.rb +327 -0
  12. data/lib/rocketamf/remoting.rb +133 -0
  13. data/lib/rocketamf/values/array_collection.rb +9 -0
  14. data/lib/rocketamf/values/messages.rb +133 -0
  15. data/lib/rocketamf/values/typed_hash.rb +13 -0
  16. data/lib/rocketamf/version.rb +9 -0
  17. data/spec/amf/class_mapping_set_spec.rb +34 -0
  18. data/spec/amf/class_mapping_spec.rb +120 -0
  19. data/spec/amf/deserializer_spec.rb +311 -0
  20. data/spec/amf/request_spec.rb +25 -0
  21. data/spec/amf/response_spec.rb +68 -0
  22. data/spec/amf/serializer_spec.rb +328 -0
  23. data/spec/amf/values/array_collection_spec.rb +6 -0
  24. data/spec/amf/values/messages_spec.rb +31 -0
  25. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  26. data/spec/fixtures/objects/amf0-date.bin +0 -0
  27. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  28. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  29. data/spec/fixtures/objects/amf0-null.bin +1 -0
  30. data/spec/fixtures/objects/amf0-number.bin +0 -0
  31. data/spec/fixtures/objects/amf0-object.bin +0 -0
  32. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  33. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  34. data/spec/fixtures/objects/amf0-string.bin +0 -0
  35. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  36. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  37. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  38. data/spec/fixtures/objects/amf3-0.bin +0 -0
  39. data/spec/fixtures/objects/amf3-arrayRef.bin +1 -0
  40. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  41. data/spec/fixtures/objects/amf3-date.bin +0 -0
  42. data/spec/fixtures/objects/amf3-datesRef.bin +0 -0
  43. data/spec/fixtures/objects/amf3-dynObject.bin +2 -0
  44. data/spec/fixtures/objects/amf3-emptyArray.bin +1 -0
  45. data/spec/fixtures/objects/amf3-emptyArrayRef.bin +1 -0
  46. data/spec/fixtures/objects/amf3-emptyStringRef.bin +1 -0
  47. data/spec/fixtures/objects/amf3-false.bin +1 -0
  48. data/spec/fixtures/objects/amf3-graphMember.bin +0 -0
  49. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  50. data/spec/fixtures/objects/amf3-largeMax.bin +0 -0
  51. data/spec/fixtures/objects/amf3-largeMin.bin +0 -0
  52. data/spec/fixtures/objects/amf3-max.bin +1 -0
  53. data/spec/fixtures/objects/amf3-min.bin +0 -0
  54. data/spec/fixtures/objects/amf3-mixedArray.bin +11 -0
  55. data/spec/fixtures/objects/amf3-null.bin +1 -0
  56. data/spec/fixtures/objects/amf3-objRef.bin +0 -0
  57. data/spec/fixtures/objects/amf3-primArray.bin +1 -0
  58. data/spec/fixtures/objects/amf3-string.bin +1 -0
  59. data/spec/fixtures/objects/amf3-stringRef.bin +0 -0
  60. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  61. data/spec/fixtures/objects/amf3-true.bin +1 -0
  62. data/spec/fixtures/objects/amf3-typedObject.bin +2 -0
  63. data/spec/fixtures/request/acknowledge-response.bin +0 -0
  64. data/spec/fixtures/request/amf0-error-response.bin +0 -0
  65. data/spec/fixtures/request/commandMessage.bin +0 -0
  66. data/spec/fixtures/request/remotingMessage.bin +0 -0
  67. data/spec/fixtures/request/simple-response.bin +0 -0
  68. data/spec/spec.opts +1 -0
  69. data/spec/spec_helper.rb +32 -0
  70. metadata +133 -0
@@ -0,0 +1,36 @@
1
+ == DESCRIPTION:
2
+
3
+ A fast AMF serializer/deserializer and request/response wrappers to simplify remoting implementation.
4
+
5
+ == INSTALL:
6
+
7
+ gem install RocketAMF --source="http://gemcutter.org"
8
+
9
+ == EXAMPLE:
10
+
11
+ NEED TO WRITE SOMETHING HERE
12
+
13
+ == LICENSE:
14
+
15
+ (The MIT License)
16
+
17
+ Copyright (c) 2009 Tony Hillerson based on work by Aaron Smith
18
+
19
+ Permission is hereby granted, free of charge, to any person obtaining
20
+ a copy of this software and associated documentation files (the
21
+ 'Software'), to deal in the Software without restriction, including
22
+ without limitation the rights to use, copy, modify, merge, publish,
23
+ distribute, sublicense, and/or sell copies of the Software, and to
24
+ permit persons to whom the Software is furnished to do so, subject to
25
+ the following conditions:
26
+
27
+ The above copyright notice and this permission notice shall be
28
+ included in all copies or substantial portions of the Software.
29
+
30
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
31
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
33
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
34
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
35
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
36
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ require 'spec/rake/spectask'
7
+
8
+ desc 'Default: run the specs.'
9
+ task :default => :spec
10
+
11
+ Spec::Rake::SpecTask.new do |t|
12
+ t.spec_opts = ['--options', 'spec/spec.opts']
13
+ end
14
+
15
+ desc 'Generate documentation for the RocketAMF plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'RocketAMF'
19
+ rdoc.options << '--line-numbers' << '--main' << 'README.rdoc'
20
+ rdoc.rdoc_files.include('README.rdoc')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ spec = Gem::Specification.new do |s|
25
+ s.name = 'RocketAMF'
26
+ s.version = '0.0.5'
27
+ s.summary = 'Fast AMF serializer/deserializer and request/response wrappers to simplify remoting implementation'
28
+
29
+ s.files = FileList['README.rdoc', 'Rakefile', 'lib/**/*.rb', 'spec/**/*.rb', 'spec/**/*.bin', 'spec/spec.opts']
30
+ s.require_path = 'lib'
31
+ s.test_files = Dir[*['spec/**/*_spec.rb']]
32
+
33
+ s.has_rdoc = true
34
+ s.extra_rdoc_files = ['README.rdoc']
35
+ s.rdoc_options = ['--line-numbers', '--main', 'README.rdoc']
36
+
37
+ s.authors = ['Tony Hillerson', 'Stephen Augenstein']
38
+ s.email = 'perl.programmer@gmail.com'
39
+ s.homepage = 'http://github.com/warhammerkid/rocket-amf'
40
+
41
+ s.platform = Gem::Platform::RUBY
42
+ end
43
+
44
+ Rake::GemPackageTask.new spec do |pkg|
45
+ pkg.need_tar = true
46
+ pkg.need_zip = true
47
+ end
48
+
49
+ desc 'Generate a gemspec file'
50
+ task :gemspec do
51
+ File.open("#{spec.name}.gemspec", 'w') do |f|
52
+ f.write spec.to_ruby
53
+ end
54
+ end
@@ -0,0 +1,12 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+ $:.unshift "#{File.expand_path(File.dirname(__FILE__))}/rocketamf/"
3
+
4
+ require 'rocketamf/common'
5
+
6
+ module RocketAMF
7
+ begin
8
+ raise LoadError, 'C extensions not implemented'
9
+ rescue LoadError
10
+ require 'rocketamf/pure'
11
+ end
12
+ end
@@ -0,0 +1,211 @@
1
+ require 'rocketamf/values/typed_hash'
2
+ require 'rocketamf/values/array_collection'
3
+ require 'rocketamf/values/messages'
4
+
5
+ module RocketAMF
6
+ # == Class Mapping
7
+ #
8
+ # Handles class name mapping between actionscript and ruby and assists in
9
+ # serializing and deserializing data between them. Simply map an AS class to a
10
+ # ruby class and when the object is (de)serialized it will end up as the
11
+ # appropriate class.
12
+ #
13
+ # Example:
14
+ #
15
+ # RocketAMF::ClassMapper.define do |m|
16
+ # m.map :as => 'AsClass', :ruby => 'RubyClass'
17
+ # m.map :as => 'vo.User', :ruby => 'User'
18
+ # end
19
+ #
20
+ # == Object Population/Serialization
21
+ #
22
+ # In addition to handling class name mapping, it also provides helper methods
23
+ # for populating ruby objects from AMF and extracting properties from ruby objects
24
+ # for serialization. Support for hash-like objects and objects using
25
+ # <tt>attr_accessor</tt> for properties is currently built in, but custom classes
26
+ # may need custom support. As such, it is possible to create a custom populator
27
+ # or serializer.
28
+ #
29
+ # Populators are processed in insert order and must respond to the <tt>can_handle?</tt>
30
+ # and <tt>populate</tt> methods.
31
+ #
32
+ # Example:
33
+ #
34
+ # class CustomPopulator
35
+ # def can_handle? obj
36
+ # true
37
+ # end
38
+ #
39
+ # def populate obj, props, dynamic_props
40
+ # obj.merge! props
41
+ # obj.merge!(dynamic_props) if dynamic_props
42
+ # end
43
+ # end
44
+ # RocketAMF::ClassMapper.object_populators << CustomPopulator.new
45
+ #
46
+ # Serializers are also processed in insert order and must respond to the
47
+ # <tt>can_handle?</tt> and <tt>serialize</tt> methods.
48
+ #
49
+ # Example:
50
+ #
51
+ # class CustomSerializer
52
+ # def can_handle? obj
53
+ # true
54
+ # end
55
+ #
56
+ # def serialize obj
57
+ # {}
58
+ # end
59
+ # end
60
+ # RocketAMF::ClassMapper.object_serializers << CustomSerializer.new
61
+ class ClassMapping
62
+ # Container for all mapped classes
63
+ class MappingSet
64
+ def initialize #:nodoc:
65
+ @as_mappings = {}
66
+ @ruby_mappings = {}
67
+
68
+ # Map defaults
69
+ map :as => 'flex.messaging.messages.AbstractMessage', :ruby => 'RocketAMF::Values::AbstractMessage'
70
+ map :as => 'flex.messaging.messages.RemotingMessage', :ruby => 'RocketAMF::Values::RemotingMessage'
71
+ map :as => 'flex.messaging.messages.AsyncMessage', :ruby => 'RocketAMF::Values::AsyncMessage'
72
+ map :as => 'flex.messaging.messages.CommandMessage', :ruby => 'RocketAMF::Values::CommandMessage'
73
+ map :as => 'flex.messaging.messages.AcknowledgeMessage', :ruby => 'RocketAMF::Values::AcknowledgeMessage'
74
+ map :as => 'flex.messaging.messages.ErrorMessage', :ruby => 'RocketAMF::Values::ErrorMessage'
75
+ map :as => 'flex.messaging.io.ArrayCollection', :ruby => 'RocketAMF::Values::ArrayCollection'
76
+ end
77
+
78
+ # Map a given AS class to a ruby class.
79
+ #
80
+ # Use fully qualified names for both.
81
+ #
82
+ # Example:
83
+ #
84
+ # m.map :as 'com.example.Date', :ruby => 'Example::Date'
85
+ def map params
86
+ [:as, :ruby].each {|k| params[k] = params[k].to_s} # Convert params to strings
87
+ @as_mappings[params[:as]] = params[:ruby]
88
+ @ruby_mappings[params[:ruby]] = params[:as]
89
+ end
90
+
91
+ # Returns the AS class name for the given ruby class name, returing nil if
92
+ # not found
93
+ def get_as_class_name class_name #:nodoc:
94
+ @ruby_mappings[class_name.to_s]
95
+ end
96
+
97
+ # Returns the ruby class name for the given AS class name, returing nil if
98
+ # not found
99
+ def get_ruby_class_name class_name #:nodoc:
100
+ @as_mappings[class_name.to_s]
101
+ end
102
+ end
103
+
104
+ # Array of custom object populators.
105
+ attr_reader :object_populators
106
+
107
+ # Array of custom object serializers.
108
+ attr_reader :object_serializers
109
+
110
+ def initialize #:nodoc:
111
+ @object_populators = []
112
+ @object_serializers = []
113
+ end
114
+
115
+ # Define class mappings in the block. Block is passed a MappingSet object as
116
+ # the first parameter.
117
+ #
118
+ # Example:
119
+ #
120
+ # RocketAMF::ClassMapper.define do |m|
121
+ # m.map :as => 'AsClass', :ruby => 'RubyClass'
122
+ # end
123
+ def define #:yields: mapping_set
124
+ yield mappings
125
+ end
126
+
127
+ # Returns the AS class name for the given ruby object. Will also take a string
128
+ # containing the ruby class name
129
+ def get_as_class_name obj
130
+ # Get class name
131
+ if obj.is_a?(String)
132
+ ruby_class_name = obj
133
+ elsif obj.is_a?(Values::TypedHash)
134
+ ruby_class_name = obj.type
135
+ else
136
+ ruby_class_name = obj.class.name
137
+ end
138
+
139
+ # Get mapped AS class name
140
+ mappings.get_as_class_name ruby_class_name
141
+ end
142
+
143
+ # Instantiates a ruby object using the mapping configuration based on the
144
+ # source AS class name. If there is no mapping defined, it returns a hash.
145
+ def get_ruby_obj as_class_name
146
+ ruby_class_name = mappings.get_ruby_class_name as_class_name
147
+ if ruby_class_name.nil?
148
+ # Populate a simple hash, since no mapping
149
+ return Values::TypedHash.new(as_class_name)
150
+ else
151
+ ruby_class = ruby_class_name.split('::').inject(Kernel) {|scope, const_name| scope.const_get(const_name)}
152
+ return ruby_class.new
153
+ end
154
+ end
155
+
156
+ # Populates the ruby object using the given properties
157
+ def populate_ruby_obj obj, props, dynamic_props=nil
158
+ # Process custom populators
159
+ @object_populators.each do |p|
160
+ next unless p.can_handle?(obj)
161
+ p.populate obj, props, dynamic_props
162
+ return obj
163
+ end
164
+
165
+ # Fallback populator
166
+ props.merge! dynamic_props if dynamic_props
167
+ hash_like = obj.respond_to?("[]=")
168
+ props.each do |key, value|
169
+ if obj.respond_to?("#{key}=")
170
+ obj.send("#{key}=", value)
171
+ elsif hash_like
172
+ obj[key.to_sym] = value
173
+ end
174
+ end
175
+ obj
176
+ end
177
+
178
+ # Extracts all exportable properties from the given ruby object and returns
179
+ # them in a hash
180
+ def props_for_serialization ruby_obj
181
+ # Proccess custom serializers
182
+ @object_serializers.each do |s|
183
+ next unless s.can_handle?(ruby_obj)
184
+ return s.serialize(ruby_obj)
185
+ end
186
+
187
+ # Handle hashes
188
+ if ruby_obj.is_a?(Hash)
189
+ # Stringify keys to make it easier later on and allow sorting
190
+ h = {}
191
+ ruby_obj.each {|k,v| h[k.to_s] = v}
192
+ return h
193
+ end
194
+
195
+ # Fallback serializer
196
+ props = {}
197
+ @ignored_props ||= Object.new.public_methods
198
+ (ruby_obj.public_methods - @ignored_props).each do |method_name|
199
+ # Add them to the prop hash if they take no arguments
200
+ method_def = ruby_obj.method(method_name)
201
+ props[method_name] = ruby_obj.send(method_name) if method_def.arity == 0
202
+ end
203
+ props
204
+ end
205
+
206
+ private
207
+ def mappings
208
+ @mappings ||= MappingSet.new
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,35 @@
1
+ require 'rocketamf/version'
2
+ require 'rocketamf/class_mapping'
3
+ require 'rocketamf/constants'
4
+ require 'rocketamf/remoting'
5
+
6
+ module RocketAMF
7
+ class << self
8
+ # Deserialize the AMF string _source_ into a Ruby data structure and return it.
9
+ def deserialize source, amf_version = 0
10
+ if amf_version == 0
11
+ RocketAMF::Deserializer.new.deserialize(source)
12
+ elsif amf_version == 3
13
+ RocketAMF::AMF3Deserializer.new.deserialize(source)
14
+ else
15
+ raise AMFError, "unsupported version #{amf_version}"
16
+ end
17
+ end
18
+
19
+ # Serialize the given Ruby data structure _obj_ into an AMF stream
20
+ def serialize obj, amf_version = 0
21
+ if amf_version == 0
22
+ RocketAMF::Serializer.new.serialize(obj)
23
+ elsif amf_version == 3
24
+ RocketAMF::AMF3Serializer.new.serialize(obj)
25
+ else
26
+ raise AMFError, "unsupported version #{amf_version}"
27
+ end
28
+ end
29
+ end
30
+
31
+ ClassMapper = RocketAMF::ClassMapping.new
32
+
33
+ # The base exception for AMF errors.
34
+ class AMFError < StandardError; end
35
+ end
@@ -0,0 +1,47 @@
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
+
37
+ # Other AMF3 Markers
38
+ AMF3_EMPTY_STRING = 0x01
39
+ AMF3_ANONYMOUS_OBJECT = 0x01
40
+ AMF3_DYNAMIC_OBJECT = 0x0B
41
+ AMF3_CLOSE_DYNAMIC_OBJECT = 0x01
42
+ AMF3_CLOSE_DYNAMIC_ARRAY = 0x01
43
+
44
+ # Other Constants
45
+ MAX_INTEGER = 268435455
46
+ MIN_INTEGER = -268435456
47
+ end
@@ -0,0 +1,30 @@
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
7
+ # functionality in pure ruby.
8
+ module Pure
9
+ $DEBUG and warn "Using pure library for RocketAMF."
10
+ end
11
+
12
+ # Import Deserializer
13
+ Deserializer = RocketAMF::Pure::Deserializer
14
+ AMF3Deserializer = RocketAMF::Pure::AMF3Deserializer
15
+
16
+ # Import serializer
17
+ Serializer = RocketAMF::Pure::Serializer
18
+ AMF3Serializer = RocketAMF::Pure::AMF3Serializer
19
+
20
+ # Modify request and response so they can serialize/deserialize
21
+ class Request
22
+ remove_method :populate_from_stream
23
+ include RocketAMF::Pure::Request
24
+ end
25
+
26
+ class Response
27
+ remove_method :serialize
28
+ include RocketAMF::Pure::Response
29
+ end
30
+ end
@@ -0,0 +1,361 @@
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_xml source
43
+ when AMF0_TYPED_OBJECT_MARKER
44
+ read_typed_object source
45
+ when AMF0_AMF3_MARKER
46
+ AMF3Deserializer.new.deserialize(source)
47
+ end
48
+ end
49
+
50
+ private
51
+ include RocketAMF::Pure::ReadIOHelpers
52
+
53
+ def read_number source
54
+ res = read_double source
55
+ res.is_a?(Float)&&res.nan? ? nil : res # check for NaN and convert them to nil
56
+ end
57
+
58
+ def read_boolean source
59
+ read_int8(source) != 0
60
+ end
61
+
62
+ def read_string source, long=false
63
+ len = long ? read_word32_network(source) : read_word16_network(source)
64
+ source.read(len)
65
+ end
66
+
67
+ def read_object source, add_to_ref_cache=true
68
+ obj = {}
69
+ @ref_cache << obj if add_to_ref_cache
70
+ while true
71
+ key = read_string source
72
+ type = read_int8 source
73
+ break if type == AMF0_OBJECT_END_MARKER
74
+ obj[key.to_sym] = deserialize(source, type)
75
+ end
76
+ obj
77
+ end
78
+
79
+ def read_reference source
80
+ index = read_word16_network(source)
81
+ @ref_cache[index]
82
+ end
83
+
84
+ def read_hash source
85
+ len = read_word32_network(source) # Read and ignore length
86
+
87
+ # Read first pair
88
+ key = read_string source
89
+ type = read_int8 source
90
+ return [] if type == AMF0_OBJECT_END_MARKER
91
+
92
+ # We need to figure out whether this is a real hash, or whether some stupid serializer gave up
93
+ if key.to_i.to_s == key
94
+ # Array
95
+ obj = []
96
+ @ref_cache << obj
97
+
98
+ obj[key.to_i] = deserialize(source, type)
99
+ while true
100
+ key = read_string source
101
+ type = read_int8 source
102
+ break if type == AMF0_OBJECT_END_MARKER
103
+ obj[key.to_i] = deserialize(source, type)
104
+ end
105
+ else
106
+ # Hash
107
+ obj = {}
108
+ @ref_cache << obj
109
+
110
+ obj[key.to_sym] = deserialize(source, type)
111
+ while true
112
+ key = read_string source
113
+ type = read_int8 source
114
+ break if type == AMF0_OBJECT_END_MARKER
115
+ obj[key.to_sym] = deserialize(source, type)
116
+ end
117
+ end
118
+ obj
119
+ end
120
+
121
+ def read_array source
122
+ len = read_word32_network(source)
123
+ array = []
124
+ @ref_cache << array
125
+
126
+ 0.upto(len - 1) do
127
+ array << deserialize(source)
128
+ end
129
+ array
130
+ end
131
+
132
+ def read_date source
133
+ seconds = read_double(source).to_f/1000
134
+ time = Time.at(seconds)
135
+ tz = read_word16_network(source) # Unused
136
+ time
137
+ end
138
+
139
+ def read_typed_object source
140
+ # Create object to add to ref cache
141
+ class_name = read_string source
142
+ obj = ClassMapper.get_ruby_obj class_name
143
+ @ref_cache << obj
144
+
145
+ # Read object props
146
+ props = read_object source, false
147
+
148
+ # Populate object
149
+ ClassMapper.populate_ruby_obj obj, props, {}
150
+ return obj
151
+ end
152
+ end
153
+
154
+ # AMF3 implementation of deserializer, loaded automatically by the AMF0
155
+ # deserializer when needed
156
+ class AMF3Deserializer
157
+ def initialize
158
+ @string_cache = []
159
+ @object_cache = []
160
+ @trait_cache = []
161
+ end
162
+
163
+ def deserialize(source, type=nil)
164
+ source = StringIO.new(source) unless StringIO === source
165
+ type = read_int8 source unless type
166
+ case type
167
+ when AMF3_UNDEFINED_MARKER
168
+ nil
169
+ when AMF3_NULL_MARKER
170
+ nil
171
+ when AMF3_FALSE_MARKER
172
+ false
173
+ when AMF3_TRUE_MARKER
174
+ true
175
+ when AMF3_INTEGER_MARKER
176
+ read_integer source
177
+ when AMF3_DOUBLE_MARKER
178
+ read_number source
179
+ when AMF3_STRING_MARKER
180
+ read_string source
181
+ when AMF3_XML_DOC_MARKER
182
+ #read_xml_string
183
+ when AMF3_DATE_MARKER
184
+ read_date source
185
+ when AMF3_ARRAY_MARKER
186
+ read_array source
187
+ when AMF3_OBJECT_MARKER
188
+ read_object source
189
+ when AMF3_XML_MARKER
190
+ #read_amf3_xml
191
+ when AMF3_BYTE_ARRAY_MARKER
192
+ #read_amf3_byte_array
193
+ end
194
+ end
195
+
196
+ private
197
+ include RocketAMF::Pure::ReadIOHelpers
198
+
199
+ def read_integer source
200
+ n = 0
201
+ b = read_word8(source) || 0
202
+ result = 0
203
+
204
+ while ((b & 0x80) != 0 && n < 3)
205
+ result = result << 7
206
+ result = result | (b & 0x7f)
207
+ b = read_word8(source) || 0
208
+ n = n + 1
209
+ end
210
+
211
+ if (n < 3)
212
+ result = result << 7
213
+ result = result | b
214
+ else
215
+ #Use all 8 bits from the 4th byte
216
+ result = result << 8
217
+ result = result | b
218
+
219
+ #Check if the integer should be negative
220
+ if (result > MAX_INTEGER)
221
+ result -= (1 << 29)
222
+ end
223
+ end
224
+ result
225
+ end
226
+
227
+ def read_number source
228
+ res = read_double source
229
+ res.is_a?(Float)&&res.nan? ? nil : res # check for NaN and convert them to nil
230
+ end
231
+
232
+ def read_string source
233
+ type = read_integer source
234
+ isReference = (type & 0x01) == 0
235
+
236
+ if isReference
237
+ reference = type >> 1
238
+ return @string_cache[reference]
239
+ else
240
+ length = type >> 1
241
+ #HACK needed for ['',''] array of empty strings
242
+ #It may be better to take one more parameter that
243
+ #would specify whether or not they expect us to return
244
+ #a string
245
+ str = "" #if stringRequest
246
+ if length > 0
247
+ str = source.read(length)
248
+ @string_cache << str
249
+ end
250
+ return str
251
+ end
252
+ end
253
+
254
+ def read_array source
255
+ type = read_integer source
256
+ isReference = (type & 0x01) == 0
257
+
258
+ if isReference
259
+ reference = type >> 1
260
+ return @object_cache[reference]
261
+ else
262
+ length = type >> 1
263
+ propertyName = read_string source
264
+ if propertyName != ""
265
+ array = {}
266
+ @object_cache << array
267
+ begin
268
+ while(propertyName.length)
269
+ value = deserialize(source)
270
+ array[propertyName] = value
271
+ propertyName = read_string source
272
+ end
273
+ rescue Exception => e #end of object exception, because propertyName.length will be non existent
274
+ end
275
+ 0.upto(length - 1) do |i|
276
+ array["" + i.to_s] = deserialize(source)
277
+ end
278
+ else
279
+ array = []
280
+ @object_cache << array
281
+ 0.upto(length - 1) do
282
+ array << deserialize(source)
283
+ end
284
+ end
285
+ array
286
+ end
287
+ end
288
+
289
+ def read_object source
290
+ type = read_integer source
291
+ isReference = (type & 0x01) == 0
292
+
293
+ if isReference
294
+ reference = type >> 1
295
+ return @object_cache[reference]
296
+ else
297
+ class_type = type >> 1
298
+ class_is_reference = (class_type & 0x01) == 0
299
+
300
+ if class_is_reference
301
+ reference = class_type >> 1
302
+ class_definition = @trait_cache[reference]
303
+ else
304
+ class_name = read_string source
305
+ externalizable = (class_type & 0x02) != 0
306
+ dynamic = (class_type & 0x04) != 0
307
+ attribute_count = class_type >> 3
308
+
309
+ class_attributes = []
310
+ attribute_count.times{class_attributes << read_string(source)} # Read class members
311
+
312
+ class_definition = {"class_name" => class_name,
313
+ "members" => class_attributes,
314
+ "externalizable" => externalizable,
315
+ "dynamic" => dynamic}
316
+ @trait_cache << class_definition
317
+ end
318
+
319
+ obj = ClassMapper.get_ruby_obj class_definition["class_name"]
320
+ @object_cache << obj
321
+
322
+ if class_definition['externalizable']
323
+ obj.externalized_data = deserialize(source)
324
+ else
325
+ props = {}
326
+ class_definition['members'].each do |key|
327
+ value = deserialize(source)
328
+ props[key.to_sym] = value
329
+ end
330
+
331
+ dynamic_props = nil
332
+ if class_definition['dynamic']
333
+ dynamic_props = {}
334
+ while (key = read_string source) && key.length != 0 do # read next key
335
+ value = deserialize(source)
336
+ dynamic_props[key.to_sym] = value
337
+ end
338
+ end
339
+
340
+ ClassMapper.populate_ruby_obj obj, props, dynamic_props
341
+ end
342
+ obj
343
+ end
344
+ end
345
+
346
+ def read_date source
347
+ type = read_integer source
348
+ isReference = (type & 0x01) == 0
349
+ if isReference
350
+ reference = type >> 1
351
+ return @object_cache[reference]
352
+ else
353
+ seconds = read_double(source).to_f/1000
354
+ time = Time.at(seconds)
355
+ @object_cache << time
356
+ time
357
+ end
358
+ end
359
+ end
360
+ end
361
+ end