mrpin-rocketamf 1.0.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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