mrpin-rocketamf 1.0.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +1 -1
  3. data/Rakefile +7 -7
  4. data/benchmark.rb +44 -37
  5. data/ext/rocketamf_ext/class_mapping.c +11 -11
  6. data/ext/rocketamf_ext/remoting.c +1 -1
  7. data/lib/rocketamf.rb +41 -98
  8. data/lib/rocketamf/constants.rb +20 -20
  9. data/lib/rocketamf/errors.rb +2 -0
  10. data/lib/rocketamf/errors/amf_error.rb +5 -0
  11. data/lib/rocketamf/errors/amf_error_incomplete.rb +5 -0
  12. data/lib/rocketamf/ext.rb +0 -6
  13. data/lib/rocketamf/extensions.rb +4 -4
  14. data/lib/rocketamf/{class_mapping.rb → mapping/class_mapping.rb} +70 -103
  15. data/lib/rocketamf/mapping/mapping_set.rb +63 -0
  16. data/lib/rocketamf/pure.rb +1 -9
  17. data/lib/rocketamf/pure/deserializer.rb +234 -262
  18. data/lib/rocketamf/pure/helpers/io_helper_base.rb +19 -0
  19. data/lib/rocketamf/pure/helpers/io_helper_read.rb +67 -0
  20. data/lib/rocketamf/pure/helpers/io_helper_write.rb +48 -0
  21. data/lib/rocketamf/pure/helpers/object_cache.rb +20 -0
  22. data/lib/rocketamf/pure/helpers/string_cache.rb +14 -0
  23. data/lib/rocketamf/pure/serializer.rb +138 -271
  24. data/lib/rocketamf/types.rb +1 -0
  25. data/lib/rocketamf/{values → types}/typed_hash.rb +12 -2
  26. data/mrpin-rocketamf.gemspec +27 -16
  27. data/spec/class_mapping_spec.rb +59 -52
  28. data/spec/deserializer_spec.rb +164 -328
  29. data/spec/fast_class_mapping_spec.rb +52 -46
  30. data/spec/helpers/class_mapping_test.rb +4 -0
  31. data/spec/helpers/class_mapping_test2.rb +3 -0
  32. data/spec/helpers/externalizable_test.rb +24 -0
  33. data/spec/helpers/fixtures.rb +28 -0
  34. data/spec/helpers/other_class.rb +4 -0
  35. data/spec/helpers/ruby_class.rb +4 -0
  36. data/spec/helpers/test_ruby_class.rb +4 -0
  37. data/spec/serializer_spec.rb +248 -339
  38. data/spec/spec_helper.rb +4 -49
  39. metadata +47 -53
  40. data/lib/rocketamf/pure/io_helpers.rb +0 -94
  41. data/lib/rocketamf/pure/remoting.rb +0 -117
  42. data/lib/rocketamf/remoting.rb +0 -196
  43. data/lib/rocketamf/values/messages.rb +0 -214
  44. data/spec/fixtures/objects/amf0-boolean.bin +0 -1
  45. data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
  46. data/spec/fixtures/objects/amf0-date.bin +0 -0
  47. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  48. data/spec/fixtures/objects/amf0-empty-string-key-hash.bin +0 -0
  49. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  50. data/spec/fixtures/objects/amf0-null.bin +0 -1
  51. data/spec/fixtures/objects/amf0-number.bin +0 -0
  52. data/spec/fixtures/objects/amf0-object.bin +0 -0
  53. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  54. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  55. data/spec/fixtures/objects/amf0-string.bin +0 -0
  56. data/spec/fixtures/objects/amf0-time.bin +0 -0
  57. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  58. data/spec/fixtures/objects/amf0-undefined.bin +0 -1
  59. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  60. data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
  61. data/spec/messages_spec.rb +0 -39
  62. data/spec/remoting_spec.rb +0 -196
@@ -0,0 +1,2 @@
1
+ require 'rocketamf/errors/amf_error'
2
+ require 'rocketamf/errors/amf_error_incomplete'
@@ -0,0 +1,5 @@
1
+ module RocketAMF
2
+ # The standard AMF error.
3
+ class AMFError < StandardError
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module RocketAMF
2
+ # Error for incomplete messages
3
+ class AMFErrorIncomplete < AMFError
4
+ end
5
+ end
data/lib/rocketamf/ext.rb CHANGED
@@ -18,11 +18,5 @@ module RocketAMF
18
18
  Deserializer = RocketAMF::Ext::Deserializer
19
19
  Serializer = RocketAMF::Ext::Serializer
20
20
 
21
- # Modify envelope so it can serialize/deserialize
22
- class Envelope
23
- remove_method :populate_from_stream
24
- remove_method :serialize
25
- include RocketAMF::Ext::Envelope
26
- end
27
21
  #:startdoc:
28
22
  end
@@ -1,5 +1,5 @@
1
1
  # Joc's monkeypatch for string bytesize (only available in 1.8.7+)
2
- if !"amf".respond_to? :bytesize
2
+ unless 'amf'.respond_to?(:bytesize)
3
3
  class String #:nodoc:
4
4
  def bytesize
5
5
  self.size
@@ -9,11 +9,11 @@ end
9
9
 
10
10
  # Add <tt>ArrayCollection</tt> override to arrays
11
11
  class Array
12
- # Override <tt>RocketAMF::ClassMapper.use_array_collection</tt> setting for
12
+ # Override <tt>RocketAMF::CLASS_MAPPER.use_array_collection</tt> setting for
13
13
  # this array. Adds <tt>is_array_collection?</tt> method, which is used by the
14
14
  # serializer over the global config if defined.
15
- def is_array_collection= a
16
- @is_array_collection = a
15
+ def is_array_collection=(value)
16
+ @is_array_collection = value
17
17
 
18
18
  def self.is_array_collection? #:nodoc:
19
19
  @is_array_collection
@@ -1,67 +1,17 @@
1
- require 'rocketamf/values/typed_hash'
2
- require 'rocketamf/values/messages'
1
+ require 'rocketamf/mapping/mapping_set'
3
2
 
4
3
  module RocketAMF
5
- # Container for all mapped classes
6
- class MappingSet
7
- # Creates a mapping set object and populates the default mappings
8
- def initialize
9
- @as_mappings = {}
10
- @ruby_mappings = {}
11
- map_defaults
12
- end
13
-
14
- # Adds required mapping configs, calling map for the required base mappings.
15
- # Designed to allow extenders to take advantage of required default mappings.
16
- def map_defaults
17
- map :as => 'flex.messaging.messages.AbstractMessage', :ruby => 'RocketAMF::Values::AbstractMessage'
18
- map :as => 'flex.messaging.messages.RemotingMessage', :ruby => 'RocketAMF::Values::RemotingMessage'
19
- map :as => 'flex.messaging.messages.AsyncMessage', :ruby => 'RocketAMF::Values::AsyncMessage'
20
- map :as => 'DSA', :ruby => 'RocketAMF::Values::AsyncMessageExt'
21
- map :as => 'flex.messaging.messages.CommandMessage', :ruby => 'RocketAMF::Values::CommandMessage'
22
- map :as => 'DSC', :ruby => 'RocketAMF::Values::CommandMessageExt'
23
- map :as => 'flex.messaging.messages.AcknowledgeMessage', :ruby => 'RocketAMF::Values::AcknowledgeMessage'
24
- map :as => 'DSK', :ruby => 'RocketAMF::Values::AcknowledgeMessageExt'
25
- map :as => 'flex.messaging.messages.ErrorMessage', :ruby => 'RocketAMF::Values::ErrorMessage'
26
- self
27
- end
28
-
29
- # Map a given AS class to a ruby class.
30
- #
31
- # Use fully qualified names for both.
32
- #
33
- # Example:
34
- #
35
- # m.map :as => 'com.example.Date', :ruby => 'Example::Date'
36
- def map params
37
- [:as, :ruby].each {|k| params[k] = params[k].to_s} # Convert params to strings
38
- @as_mappings[params[:as]] = params[:ruby]
39
- @ruby_mappings[params[:ruby]] = params[:as]
40
- end
41
-
42
- # Returns the AS class name for the given ruby class name, returing nil if
43
- # not found
44
- def get_as_class_name class_name #:nodoc:
45
- @ruby_mappings[class_name.to_s]
46
- end
47
-
48
- # Returns the ruby class name for the given AS class name, returing nil if
49
- # not found
50
- def get_ruby_class_name class_name #:nodoc:
51
- @as_mappings[class_name.to_s]
52
- end
53
- end
54
4
 
55
- # Handles class name mapping between actionscript and ruby and assists in
5
+ # Handles class name mapping between AS and ruby and assists in
56
6
  # serializing and deserializing data between them. Simply map an AS class to a
57
7
  # ruby class and when the object is (de)serialized it will end up as the
58
8
  # appropriate class.
59
9
  #
60
10
  # Example:
61
11
  #
62
- # RocketAMF::ClassMapper.define do |m|
63
- # m.map :as => 'AsClass', :ruby => 'RubyClass'
64
- # m.map :as => 'vo.User', :ruby => 'Model::User'
12
+ # RocketAMF::CLASS_MAPPER.define do |m|
13
+ # m.map as: 'AsClass', ruby: 'RubyClass'
14
+ # m.map as: 'vo.User', ruby: 'Model::User'
65
15
  # end
66
16
  #
67
17
  # == Object Population/Serialization
@@ -76,8 +26,8 @@ module RocketAMF
76
26
  #
77
27
  # In some cases, it may be beneficial to replace the default provider of class
78
28
  # mapping completely. In this case, simply assign your class mapper class to
79
- # <tt>RocketAMF::ClassMapper</tt> after loading RocketAMF. Through the magic of
80
- # <tt>const_missing</tt>, <tt>ClassMapper</tt> is only defined after the first
29
+ # <tt>RocketAMF::CLASS_MAPPER</tt> after loading RocketAMF. Through the magic of
30
+ # <tt>const_missing</tt>, <tt>CLASS_MAPPER</tt> is only defined after the first
81
31
  # access by default, so you get no annoying warning messages. Custom class mappers
82
32
  # must implement the following methods on instances: <tt>use_array_collection</tt>,
83
33
  # <tt>get_as_class_name</tt>, <tt>get_ruby_obj</tt>, <tt>populate_ruby_obj</tt>,
@@ -90,10 +40,8 @@ module RocketAMF
90
40
  #
91
41
  # require 'rubygems'
92
42
  # require 'rocketamf'
93
- #
94
- # RocketAMF::ClassMapper = MyCustomClassMapper
95
- # # No warning about already initialized constant ClassMapper
96
- # RocketAMF::ClassMapper # MyCustomClassMapper
43
+ #
44
+ # RocketAMF.const_set(CLASS_MAPPER, MyCustomClassMapper)
97
45
  #
98
46
  # == C ClassMapper
99
47
  #
@@ -113,6 +61,7 @@ module RocketAMF
113
61
  #
114
62
  # require 'rubygems'
115
63
  # require 'rocketamf'
64
+ #todo:review
116
65
  # RocketAMF::ClassMapper = RocketAMF::Ext::FastClassMapping
117
66
  class ClassMapping
118
67
  class << self
@@ -131,10 +80,10 @@ module RocketAMF
131
80
  #
132
81
  # Example:
133
82
  #
134
- # RocketAMF::ClassMapper.define do |m|
135
- # m.map :as => 'AsClass', :ruby => 'RubyClass'
83
+ # RocketAMF::CLASS_MAPPER.define do |m|
84
+ # m.map as: 'AsClass', ruby: 'RubyClass'
136
85
  # end
137
- def define &block #:yields: mapping_set
86
+ def define(&block) #:yields: mapping_set
138
87
  yield mappings
139
88
  end
140
89
 
@@ -142,25 +91,34 @@ module RocketAMF
142
91
  # <tt>use_array_collection</tt> to false
143
92
  def reset
144
93
  @use_array_collection = false
145
- @mappings = nil
94
+ @mappings = nil
146
95
  end
147
96
  end
148
97
 
98
+ #
99
+ # Properties
100
+ #
101
+
149
102
  attr_reader :use_array_collection
150
103
 
104
+ #
105
+ # Methods
106
+ #
107
+
151
108
  # Copies configuration from class level configs to populate object
109
+ public
152
110
  def initialize
153
- @mappings = self.class.mappings
111
+ @mappings = self.class.mappings
154
112
  @use_array_collection = self.class.use_array_collection === true
155
113
  end
156
114
 
157
115
  # Returns the ActionScript class name for the given ruby object. Will also
158
116
  # take a string containing the ruby class name.
159
- def get_as_class_name obj
117
+ def get_as_class_name(obj)
160
118
  # Get class name
161
119
  if obj.is_a?(String)
162
120
  ruby_class_name = obj
163
- elsif obj.is_a?(Values::TypedHash)
121
+ elsif obj.is_a?(Types::TypedHash)
164
122
  ruby_class_name = obj.type
165
123
  elsif obj.is_a?(Hash)
166
124
  return nil
@@ -169,44 +127,49 @@ module RocketAMF
169
127
  end
170
128
 
171
129
  # Get mapped AS class name
172
- @mappings.get_as_class_name ruby_class_name
130
+ @mappings.get_as_class_name(ruby_class_name)
173
131
  end
174
132
 
175
133
  # Instantiates a ruby object using the mapping configuration based on the
176
134
  # source ActionScript class name. If there is no mapping defined, it returns
177
- # a <tt>RocketAMF::Values::TypedHash</tt> with the serialized class name.
178
- def get_ruby_obj as_class_name
179
- ruby_class_name = @mappings.get_ruby_class_name as_class_name
135
+ # a <tt>RocketAMF::Types::TypedHash</tt> with the serialized class name.
136
+ public
137
+ def get_ruby_obj(as_class_name)
138
+ result = nil
139
+
140
+ ruby_class_name = @mappings.get_ruby_class_name(as_class_name)
180
141
  if ruby_class_name.nil?
181
142
  # Populate a simple hash, since no mapping
182
- return Values::TypedHash.new(as_class_name)
143
+ result = Types::TypedHash.new(as_class_name)
183
144
  else
184
- ruby_class = ruby_class_name.split('::').inject(Kernel) {|scope, const_name| scope.const_get(const_name)}
185
- return ruby_class.new
145
+ ruby_class = ruby_class_name.split('::').inject(Kernel) { |scope, const_name| scope.const_get(const_name) }
146
+ result = ruby_class.new
186
147
  end
148
+
149
+ result
187
150
  end
188
151
 
189
- # Populates the ruby object using the given properties. props and
190
- # dynamic_props will be hashes with symbols for keys.
191
- def populate_ruby_obj obj, props, dynamic_props=nil
192
- props.merge! dynamic_props if dynamic_props
152
+ # Populates the ruby object using the given properties. props will be hashes with symbols for keys.
153
+ public
154
+ def populate_ruby_obj(target, props)
193
155
 
194
156
  # Don't even bother checking if it responds to setter methods if it's a TypedHash
195
- if obj.is_a?(Values::TypedHash)
196
- obj.merge! props
197
- return obj
157
+ if target.is_a?(Types::TypedHash)
158
+ target.merge! props
159
+ return target
198
160
  end
199
161
 
200
162
  # Some type of object
201
- hash_like = obj.respond_to?("[]=")
163
+ hash_like = target.respond_to?("[]=")
202
164
  props.each do |key, value|
203
- if obj.respond_to?("#{key}=")
204
- obj.send("#{key}=", value)
165
+ if target.respond_to?("#{key}=")
166
+ target.send("#{key}=", value)
205
167
  elsif hash_like
206
- obj[key] = value
168
+ target[key] = value
207
169
  end
208
170
  end
209
- obj
171
+
172
+ target
210
173
  end
211
174
 
212
175
  # Extracts all exportable properties from the given ruby object and returns
@@ -214,24 +177,28 @@ module RocketAMF
214
177
  # unless you are only going to be using the native C extensions, as the pure
215
178
  # ruby serializer performs a sort on the keys to acheive consistent, testable
216
179
  # results.
217
- def props_for_serialization ruby_obj
180
+ public
181
+ def props_for_serialization(ruby_obj)
182
+ result = {}
183
+
218
184
  # Handle hashes
219
185
  if ruby_obj.is_a?(Hash)
186
+
220
187
  # Stringify keys to make it easier later on and allow sorting
221
- h = {}
222
- ruby_obj.each {|k,v| h[k.to_s] = v}
223
- return h
224
- end
188
+ ruby_obj.each { |k, v| result[k.to_s] = v }
189
+
190
+ else
225
191
 
226
- # Generic object serializer
227
- props = {}
228
- @ignored_props ||= Object.new.public_methods
229
- (ruby_obj.public_methods - @ignored_props).each do |method_name|
230
- # Add them to the prop hash if they take no arguments
231
- method_def = ruby_obj.method(method_name)
232
- props[method_name.to_s] = ruby_obj.send(method_name) if method_def.arity == 0
192
+ # Generic object serializer
193
+ @ignored_props ||= Object.new.public_methods
194
+ (ruby_obj.public_methods - @ignored_props).each do |method_name|
195
+ # Add them to the prop hash if they take no arguments
196
+ method_def = ruby_obj.method(method_name)
197
+ result[method_name.to_s] = ruby_obj.send(method_name) if method_def.arity == 0
198
+ end
233
199
  end
234
- props
235
- end
236
- end
237
- end
200
+
201
+ result
202
+ end # props_for_serialization
203
+ end #ClassMapping
204
+ end #RocketAMF
@@ -0,0 +1,63 @@
1
+ module RocketAMF
2
+
3
+ # Container for all mapped classes
4
+ class MappingSet
5
+
6
+ #
7
+ # Methods
8
+ #
9
+
10
+ # Creates a mapping set object and populates the default mappings
11
+ public
12
+ def initialize
13
+ @as_mappings = {}
14
+ @ruby_mappings = {}
15
+ map_defaults
16
+ end
17
+
18
+ # Adds required mapping configs, calling map for the required base mappings.
19
+ # Designed to allow extenders to take advantage of required default mappings.
20
+ public
21
+ def map_defaults
22
+ map as: 'flex.messaging.messages.AbstractMessage', ruby: 'RocketAMF::Types::AbstractMessage'
23
+ map as: 'flex.messaging.messages.RemotingMessage', ruby: 'RocketAMF::Types::RemotingMessage'
24
+ map as: 'flex.messaging.messages.AsyncMessage', ruby: 'RocketAMF::Types::AsyncMessage'
25
+ map as: 'DSA', ruby: 'RocketAMF::Types::AsyncMessageExt'
26
+ map as: 'flex.messaging.messages.CommandMessage', ruby: 'RocketAMF::Types::CommandMessage'
27
+ map as: 'DSC', ruby: 'RocketAMF::Types::CommandMessageExt'
28
+ map as: 'flex.messaging.messages.AcknowledgeMessage', ruby: 'RocketAMF::Types::AcknowledgeMessage'
29
+ map as: 'DSK', ruby: 'RocketAMF::Types::AcknowledgeMessageExt'
30
+ map as: 'flex.messaging.messages.ErrorMessage', ruby: 'RocketAMF::Types::ErrorMessage'
31
+ self
32
+ end
33
+
34
+ # Map a given AS class to a ruby class.
35
+ #
36
+ # Use fully qualified names for both.
37
+ #
38
+ # Example:
39
+ #
40
+ # m.map as: 'com.example.Date', ruby: 'Example::Date'
41
+ public
42
+ def map(params)
43
+ [:as, :ruby].each { |k| params[k] = params[k].to_s } # Convert params to strings
44
+ @as_mappings[params[:as]] = params[:ruby]
45
+ @ruby_mappings[params[:ruby]] = params[:as]
46
+ end
47
+
48
+ # Returns the AS class name for the given ruby class name,
49
+ # returning nil if not found
50
+ public
51
+ def get_as_class_name(class_name) #:nodoc:
52
+ @ruby_mappings[class_name.to_s]
53
+ end
54
+
55
+ # Returns the ruby class name for the given AS class name
56
+ # returning nil if not found
57
+ public
58
+ def get_ruby_class_name(class_name) #:nodoc:
59
+ @as_mappings[class_name.to_s]
60
+ end
61
+ end
62
+
63
+ end
@@ -1,24 +1,16 @@
1
1
  require 'rocketamf/pure/deserializer'
2
2
  require 'rocketamf/pure/serializer'
3
- require 'rocketamf/pure/remoting'
4
3
 
5
4
  module RocketAMF
6
5
  # This module holds all the modules/classes that implement AMF's functionality
7
6
  # in pure ruby
8
7
  module Pure
9
- $DEBUG and warn "Using pure library for RocketAMF."
8
+ $DEBUG and warn 'Using pure library for RocketAMF.'
10
9
  end
11
10
 
12
11
  #:stopdoc:
13
12
  # Import serializer/deserializer
14
13
  Deserializer = RocketAMF::Pure::Deserializer
15
14
  Serializer = RocketAMF::Pure::Serializer
16
-
17
- # Modify envelope so it can serialize/deserialize
18
- class Envelope
19
- remove_method :populate_from_stream
20
- remove_method :serialize
21
- include RocketAMF::Pure::Envelope
22
- end
23
15
  #:startdoc:
24
16
  end
@@ -1,182 +1,80 @@
1
- require 'rocketamf/pure/io_helpers'
1
+ require 'rocketamf/pure/helpers/io_helper_read'
2
2
 
3
3
  module RocketAMF
4
4
  module Pure
5
- # Pure ruby deserializer for AMF0 and AMF3
5
+ # Pure ruby deserializer for AMF3 requests
6
6
  class Deserializer
7
- attr_accessor :source
7
+
8
+ #
9
+ # Modules
10
+ #
11
+ private
12
+ include RocketAMF::Pure::IOHelperRead
13
+
14
+ #
15
+ # Properties
16
+ #
17
+
18
+ public
19
+ attr_reader :source
8
20
 
9
21
  # Pass in the class mapper instance to use when deserializing. This
10
22
  # enables better caching behavior in the class mapper and allows
11
23
  # one to change mappings between deserialization attempts.
24
+ public
12
25
  def initialize(class_mapper)
13
26
  @class_mapper = class_mapper
14
27
  end
15
28
 
16
- # Deserialize the source using AMF0 or AMF3. Source should either
29
+ # Deserialize the source using AMF3. Source should either
17
30
  # be a string or StringIO object. If you pass a StringIO object,
18
31
  # it will have its position updated to the end of the deserialized
19
32
  # data.
20
- def deserialize(version, source)
21
- result = []
22
-
23
- @version = version
24
-
25
- if source.is_a?(StringIO)
26
- @source = source
27
- elsif source
28
- @source = StringIO.new(source)
29
- elsif @source.nil?
30
- raise AMFError, 'no source to deserialize'
31
- end
32
-
33
- case @version
34
- when 0
35
- until @source.eof?
36
- @ref_cache = []
37
- result << amf0_deserialize
38
- end
39
- when 3
40
- until @source.eof?
41
- @string_cache = []
42
- @object_cache = []
43
- @trait_cache = []
44
- result << amf3_deserialize
45
- end
46
- else
47
- raise ArgumentError, "unsupported version #{version}"
48
- end
49
-
50
- result
51
- end
52
-
53
- # Reads an object from the deserializer's stream and returns it.
54
- def read_object
55
- @version == 0 ? amf0_deserialize : amf3_deserialize
56
- end
57
-
58
- private
59
- include RocketAMF::Pure::ReadIOHelpers
33
+ # raise AMFError if error appeared in deserialize, source is nil
34
+ # return hash {requests: [], incomplete_request: String}
35
+ public
36
+ def deserialize(source)
37
+ raise AMFError, 'no source to deserialize' if source.nil?
60
38
 
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
98
-
99
- def amf0_read_number
100
- result = read_double @source
101
- (result.is_a?(Float) && result.nan?) ? nil : result # check for NaN and convert them to nil
102
- end
39
+ @source = source.is_a?(StringIO) ? source : StringIO.new(source)
103
40
 
104
- def amf0_read_boolean
105
- read_int8(@source) != 0
106
- end
41
+ requests = []
107
42
 
108
- def amf0_read_string(long=false)
109
- len = long ? read_word32_network(@source) : read_word16_network(@source)
110
- result = @source.read(len)
111
- result.force_encoding('UTF-8') if result.respond_to?(:force_encoding)
112
- result
113
- end
43
+ incomplete_request = nil
114
44
 
115
- def amf0_read_reference
116
- index = read_word16_network(@source)
117
- @ref_cache[index]
118
- end
45
+ until @source.eof?
46
+ begin
47
+ @string_cache = []
48
+ @object_cache = []
49
+ @trait_cache = []
119
50
 
120
- def amf0_read_array
121
- len = read_word32_network(@source)
122
- result = []
123
- @ref_cache << result
51
+ @position_request_read = @source.pos
124
52
 
125
- 0.upto(len - 1) do
126
- result << amf0_deserialize
127
- end
128
- result
129
- end
53
+ requests << amf3_deserialize
54
+ rescue AMFErrorIncomplete => e
55
+ @source.pos = @position_request_read
130
56
 
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
57
+ incomplete_request = @source.read
137
58
 
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)
59
+ break
60
+ end
144
61
  end
145
- obj
146
- end
147
62
 
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
63
+ {
64
+ requests: requests,
65
+ incomplete_request: incomplete_request
66
+ }
153
67
  end
154
68
 
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
165
-
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
69
+ # Reads an object from the deserializer stream and returns it.
70
+ public
71
+ def read_object
72
+ amf3_deserialize
176
73
  end
177
74
 
75
+ private
178
76
  def amf3_deserialize
179
- type = read_int8 @source
77
+ type = read_int8(@source)
180
78
  case type
181
79
  when AMF3_UNDEFINED_MARKER
182
80
  nil
@@ -203,7 +101,7 @@ module RocketAMF
203
101
  when AMF3_BYTE_ARRAY_MARKER
204
102
  amf3_read_byte_array
205
103
  when AMF3_VECTOR_INT_MARKER, AMF3_VECTOR_UINT_MARKER, AMF3_VECTOR_DOUBLE_MARKER, AMF3_VECTOR_OBJECT_MARKER
206
- amf3_read_vector type
104
+ amf3_read_vector(type)
207
105
  when AMF3_DICT_MARKER
208
106
  amf3_read_dict
209
107
  else
@@ -211,12 +109,38 @@ module RocketAMF
211
109
  end
212
110
  end
213
111
 
112
+ private
113
+ def get_as_reference_object(type)
114
+ result = nil
115
+
116
+ if (type & 0x01) == 0 #is reference?
117
+ reference = type >> 1
118
+ result = @object_cache[reference]
119
+ end
120
+
121
+ result
122
+ end
123
+
124
+ private
125
+ def get_as_reference_string(type)
126
+ result = nil
127
+
128
+ if (type & 0x01) == 0 #is reference?
129
+ reference = type >> 1
130
+ result = @string_cache[reference]
131
+ end
132
+
133
+ result
134
+ end
135
+
136
+ private
214
137
  def amf3_read_integer
215
- n = 0
216
- b = read_word8(@source) || 0
217
138
  result = 0
218
139
 
219
- while ((b & 0x80) != 0 && n < 3)
140
+ n = 0
141
+ b = read_word8(@source) || 0
142
+
143
+ while (b & 0x80) != 0 && n < 3
220
144
  result = result << 7
221
145
  result = result | (b & 0x7f)
222
146
  b = read_word8(@source) || 0
@@ -236,103 +160,135 @@ module RocketAMF
236
160
  result -= (1 << 29)
237
161
  end
238
162
  end
163
+
239
164
  result
240
165
  end
241
166
 
167
+ private
242
168
  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
169
+ result = read_double(@source)
170
+
171
+ #check for NaN and convert them to nil
172
+ if result.is_a?(Float) && result.nan?
173
+ result = nil
174
+ end
175
+
176
+ result
245
177
  end
246
178
 
179
+ private
247
180
  def amf3_read_string
248
- type = amf3_read_integer
249
- is_reference = (type & 0x01) == 0
181
+ result = nil
250
182
 
251
- if is_reference
252
- reference = type >> 1
253
- return @string_cache[reference]
254
- else
183
+ type = amf3_read_integer
184
+
185
+ result = get_as_reference_string(type)
186
+
187
+ if result.nil?
255
188
  length = type >> 1
256
- str = ''
189
+ result = ''
190
+
257
191
  if length > 0
258
- str = @source.read(length)
259
- str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
260
- @string_cache << str
192
+
193
+ if length > (@source.size - @source.pos)
194
+ raise AMFErrorIncomplete.new
195
+ end
196
+
197
+ result = @source.read(length)
198
+ result.force_encoding('UTF-8') if result.respond_to?(:force_encoding)
199
+ @string_cache << result
261
200
  end
262
- return str
263
201
  end
202
+
203
+ result
264
204
  end
265
205
 
206
+ private
266
207
  def amf3_read_xml
267
208
  result = nil
268
209
 
269
- type = amf3_read_integer
270
- is_reference = (type & 0x01) == 0
210
+ type = amf3_read_integer
271
211
 
272
- if is_reference
273
- reference = type >> 1
274
- result = @object_cache[reference]
275
- else
212
+ result = get_as_reference_object(type)
213
+
214
+ if result.nil?
276
215
  length = type >> 1
277
- str = ""
216
+
217
+ result = ''
218
+
278
219
  if length > 0
279
- str = @source.read(length)
280
- str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
281
- @object_cache << str
220
+ if length > (@source.size - @source.pos)
221
+ raise AMFErrorIncomplete.new
222
+ end
223
+
224
+ result = @source.read(length)
225
+ result.force_encoding('UTF-8') if result.respond_to?(:force_encoding)
226
+ @object_cache << result
282
227
  end
283
- result = str
284
228
  end
285
229
 
286
230
  result
287
231
  end
288
232
 
233
+ private
289
234
  def amf3_read_byte_array
290
- type = amf3_read_integer
291
- is_reference = (type & 0x01) == 0
235
+ result = nil
292
236
 
293
- if is_reference
294
- reference = type >> 1
295
- return @object_cache[reference]
296
- else
237
+ type = amf3_read_integer
238
+
239
+ result = get_as_reference_object(type)
240
+
241
+ if result.nil?
297
242
  length = type >> 1
298
- obj = StringIO.new @source.read(length)
299
- @object_cache << obj
300
- obj
243
+
244
+ if length > (@source.size - @source.pos)
245
+ raise AMFErrorIncomplete.new
246
+ end
247
+
248
+ result = StringIO.new(@source.read(length))
249
+ @object_cache << result
301
250
  end
251
+
252
+ result
302
253
  end
303
254
 
255
+ private
304
256
  def amf3_read_array
305
- type = amf3_read_integer
306
- is_reference = (type & 0x01) == 0
257
+ result = nil
307
258
 
308
- if is_reference
309
- reference = type >> 1
310
- return @object_cache[reference]
311
- else
259
+ type = amf3_read_integer
260
+
261
+ result = get_as_reference_object(type)
262
+
263
+ if result.nil?
312
264
  length = type >> 1
313
265
  property_name = amf3_read_string
314
- array = property_name.length > 0 ? {} : []
315
- @object_cache << array
266
+ result = property_name.length > 0 ? {} : []
267
+ @object_cache << result
316
268
 
317
269
  while property_name.length > 0
318
- value = amf3_deserialize
319
- array[property_name] = value
320
- property_name = amf3_read_string
270
+ value = amf3_deserialize
271
+ result[property_name] = value
272
+ property_name = amf3_read_string
321
273
  end
322
- 0.upto(length - 1) { |i| array[i] = amf3_deserialize }
323
274
 
324
- array
275
+ 0.upto(length - 1) { |i| result[i] = amf3_deserialize }
325
276
  end
277
+
278
+ result
326
279
  end
327
280
 
281
+ # externalizable - an instance of a Class that implements flash.utils.IExternalizable and completely controls the serialization of its members (no property names are included in the trait information)
282
+ # dynamic - c an instance of a Class definition with the dynamic trait declared; public variable members can be added and removed from instances dynamically at runtime
283
+ private
328
284
  def amf3_read_object
329
- type = amf3_read_integer
330
- is_reference = (type & 0x01) == 0
285
+ result = nil
331
286
 
332
- if is_reference
333
- reference = type >> 1
334
- return @object_cache[reference]
335
- else
287
+ type = amf3_read_integer
288
+
289
+ result = get_as_reference_object(type)
290
+
291
+ if result.nil?
336
292
  class_type = type >> 1
337
293
  class_is_reference = (class_type & 0x01) == 0
338
294
 
@@ -359,108 +315,124 @@ module RocketAMF
359
315
  end
360
316
 
361
317
  # Optimization for deserializing ArrayCollection
362
- if traits[:class_name] == "flex.messaging.io.ArrayCollection"
363
- arr = amf3_deserialize # Adds ArrayCollection array to object cache
364
- @object_cache << arr # Add again for ArrayCollection source array
365
- return arr
318
+ if traits[:class_name] == 'flex.messaging.io.ArrayCollection'
319
+ result = amf3_deserialize # Adds ArrayCollection array to object cache
320
+ @object_cache << result # Add again for ArrayCollection source array
321
+ return result
366
322
  end
367
323
 
368
- obj = @class_mapper.get_ruby_obj traits[:class_name]
369
- @object_cache << obj
324
+ result = @class_mapper.get_ruby_obj(traits[:class_name])
325
+ @object_cache << result
370
326
 
371
327
  if traits[:externalizable]
372
- obj.read_external self
328
+ result.read_external(self)
373
329
  else
374
- props = {}
330
+ properties = {}
331
+
375
332
  traits[:members].each do |key|
376
- value = amf3_deserialize
377
- props[key] = value
333
+ value = amf3_deserialize
334
+ properties[key] = value
378
335
  end
379
336
 
380
- dynamic_props = nil
381
337
  if traits[:dynamic]
382
- dynamic_props = {}
383
338
  while (key = amf3_read_string) && key.length != 0 do # read next key
384
- value = amf3_deserialize
385
- dynamic_props[key] = value
339
+ value = amf3_deserialize
340
+ properties[key] = value
386
341
  end
387
342
  end
388
343
 
389
- @class_mapper.populate_ruby_obj obj, props, dynamic_props
344
+ @class_mapper.populate_ruby_obj(result, properties)
390
345
  end
391
- obj
346
+
392
347
  end
348
+
349
+ result
393
350
  end
394
351
 
352
+ private
395
353
  def amf3_read_date
396
- type = amf3_read_integer
397
- is_reference = (type & 0x01) == 0
398
- if is_reference
399
- reference = type >> 1
400
- return @object_cache[reference]
401
- else
354
+ result = nil
355
+
356
+ type = amf3_read_integer
357
+
358
+ result = get_as_reference_object(type)
359
+
360
+ if result.nil?
402
361
  seconds = read_double(@source).to_f/1000
403
- time = Time.at(seconds)
404
- @object_cache << time
405
- time
362
+ result = Time.at(seconds)
363
+ @object_cache << result
406
364
  end
365
+
366
+ result
407
367
  end
408
368
 
369
+ private
409
370
  def amf3_read_dict
410
- type = amf3_read_integer
411
- is_reference = (type & 0x01) == 0
412
- if is_reference
413
- reference = type >> 1
414
- return @object_cache[reference]
415
- else
416
- dict = {}
417
- @object_cache << dict
371
+ result = nil
372
+
373
+ type = amf3_read_integer
374
+
375
+ result = get_as_reference_object(type)
376
+
377
+ if result.nil?
378
+ result = {}
379
+ @object_cache << result
418
380
  length = type >> 1
419
- weak_keys = read_int8 @source # Ignore: Not supported in ruby
381
+ weak_keys = read_int8(@source) # Ignore: Not supported in ruby
382
+
420
383
  0.upto(length - 1) do |i|
421
- dict[amf3_deserialize] = amf3_deserialize
384
+ result[amf3_deserialize] = amf3_deserialize
422
385
  end
423
- dict
386
+
424
387
  end
388
+
389
+ result
425
390
  end
426
391
 
427
- def amf3_read_vector vector_type
428
- type = amf3_read_integer
429
- is_reference = (type & 0x01) == 0
430
- if is_reference
431
- reference = type >> 1
432
- return @object_cache[reference]
433
- else
434
- vec = []
435
- @object_cache << vec
392
+ private
393
+ def amf3_read_vector(vector_type)
394
+ result = nil
395
+
396
+ type = amf3_read_integer
397
+
398
+ result = get_as_reference_object(type)
399
+
400
+ if result.nil?
401
+
402
+ result = []
403
+ @object_cache << result
404
+
436
405
  length = type >> 1
437
- fixed_vector = read_int8 @source # Ignore
406
+ fixed_vector = read_int8(@source) # Ignore
407
+
438
408
  case vector_type
439
409
  when AMF3_VECTOR_INT_MARKER
440
410
  0.upto(length - 1) do |i|
441
411
  int = read_word32_network(@source)
442
412
  int = int - 2**32 if int > MAX_INTEGER
443
- vec << int
413
+ result << int
444
414
  end
445
415
  when AMF3_VECTOR_UINT_MARKER
446
416
  0.upto(length - 1) do |i|
447
- vec << read_word32_network(@source)
448
- puts vec[i].to_s(2)
417
+ result << read_word32_network(@source)
449
418
  end
450
419
  when AMF3_VECTOR_DOUBLE_MARKER
451
420
  0.upto(length - 1) do |i|
452
- vec << amf3_read_number
421
+ result << amf3_read_number
453
422
  end
454
423
  when AMF3_VECTOR_OBJECT_MARKER
455
424
  vector_class = amf3_read_string # Ignore
456
- puts vector_class
457
425
  0.upto(length - 1) do |i|
458
- vec << amf3_deserialize
426
+ result << amf3_deserialize
459
427
  end
428
+ else
429
+ #do nothing
460
430
  end
461
- vec
462
- end
463
- end
464
- end
465
- end
431
+ end #if
432
+
433
+ result
434
+ end #read_vector
435
+
436
+ end #Deserializer
437
+ end #Pure
466
438
  end