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.
- checksums.yaml +4 -4
- data/README.rdoc +1 -1
- data/Rakefile +7 -7
- data/benchmark.rb +44 -37
- data/ext/rocketamf_ext/class_mapping.c +11 -11
- data/ext/rocketamf_ext/remoting.c +1 -1
- data/lib/rocketamf.rb +41 -98
- data/lib/rocketamf/constants.rb +20 -20
- data/lib/rocketamf/errors.rb +2 -0
- data/lib/rocketamf/errors/amf_error.rb +5 -0
- data/lib/rocketamf/errors/amf_error_incomplete.rb +5 -0
- data/lib/rocketamf/ext.rb +0 -6
- data/lib/rocketamf/extensions.rb +4 -4
- data/lib/rocketamf/{class_mapping.rb → mapping/class_mapping.rb} +70 -103
- data/lib/rocketamf/mapping/mapping_set.rb +63 -0
- data/lib/rocketamf/pure.rb +1 -9
- data/lib/rocketamf/pure/deserializer.rb +234 -262
- data/lib/rocketamf/pure/helpers/io_helper_base.rb +19 -0
- data/lib/rocketamf/pure/helpers/io_helper_read.rb +67 -0
- data/lib/rocketamf/pure/helpers/io_helper_write.rb +48 -0
- data/lib/rocketamf/pure/helpers/object_cache.rb +20 -0
- data/lib/rocketamf/pure/helpers/string_cache.rb +14 -0
- data/lib/rocketamf/pure/serializer.rb +138 -271
- data/lib/rocketamf/types.rb +1 -0
- data/lib/rocketamf/{values → types}/typed_hash.rb +12 -2
- data/mrpin-rocketamf.gemspec +27 -16
- data/spec/class_mapping_spec.rb +59 -52
- data/spec/deserializer_spec.rb +164 -328
- data/spec/fast_class_mapping_spec.rb +52 -46
- data/spec/helpers/class_mapping_test.rb +4 -0
- data/spec/helpers/class_mapping_test2.rb +3 -0
- data/spec/helpers/externalizable_test.rb +24 -0
- data/spec/helpers/fixtures.rb +28 -0
- data/spec/helpers/other_class.rb +4 -0
- data/spec/helpers/ruby_class.rb +4 -0
- data/spec/helpers/test_ruby_class.rb +4 -0
- data/spec/serializer_spec.rb +248 -339
- data/spec/spec_helper.rb +4 -49
- metadata +47 -53
- data/lib/rocketamf/pure/io_helpers.rb +0 -94
- data/lib/rocketamf/pure/remoting.rb +0 -117
- data/lib/rocketamf/remoting.rb +0 -196
- data/lib/rocketamf/values/messages.rb +0 -214
- data/spec/fixtures/objects/amf0-boolean.bin +0 -1
- data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
- data/spec/fixtures/objects/amf0-date.bin +0 -0
- data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
- data/spec/fixtures/objects/amf0-empty-string-key-hash.bin +0 -0
- data/spec/fixtures/objects/amf0-hash.bin +0 -0
- data/spec/fixtures/objects/amf0-null.bin +0 -1
- data/spec/fixtures/objects/amf0-number.bin +0 -0
- data/spec/fixtures/objects/amf0-object.bin +0 -0
- data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
- data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
- data/spec/fixtures/objects/amf0-string.bin +0 -0
- data/spec/fixtures/objects/amf0-time.bin +0 -0
- data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
- data/spec/fixtures/objects/amf0-undefined.bin +0 -1
- data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
- data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
- data/spec/messages_spec.rb +0 -39
- data/spec/remoting_spec.rb +0 -196
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
|
data/lib/rocketamf/extensions.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Joc's monkeypatch for string bytesize (only available in 1.8.7+)
|
2
|
-
|
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::
|
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=
|
16
|
-
@is_array_collection =
|
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/
|
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
|
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::
|
63
|
-
# m.map :
|
64
|
-
# m.map :
|
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::
|
80
|
-
# <tt>const_missing</tt>, <tt>
|
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
|
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::
|
135
|
-
# m.map :
|
83
|
+
# RocketAMF::CLASS_MAPPER.define do |m|
|
84
|
+
# m.map as: 'AsClass', ruby: 'RubyClass'
|
136
85
|
# end
|
137
|
-
def define
|
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
|
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
|
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
|
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?(
|
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
|
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::
|
178
|
-
|
179
|
-
|
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
|
-
|
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
|
-
|
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
|
190
|
-
|
191
|
-
def populate_ruby_obj
|
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
|
196
|
-
|
197
|
-
return
|
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 =
|
163
|
+
hash_like = target.respond_to?("[]=")
|
202
164
|
props.each do |key, value|
|
203
|
-
if
|
204
|
-
|
165
|
+
if target.respond_to?("#{key}=")
|
166
|
+
target.send("#{key}=", value)
|
205
167
|
elsif hash_like
|
206
|
-
|
168
|
+
target[key] = value
|
207
169
|
end
|
208
170
|
end
|
209
|
-
|
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
|
-
|
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
|
-
|
222
|
-
|
223
|
-
|
224
|
-
end
|
188
|
+
ruby_obj.each { |k, v| result[k.to_s] = v }
|
189
|
+
|
190
|
+
else
|
225
191
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
235
|
-
|
236
|
-
|
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
|
data/lib/rocketamf/pure.rb
CHANGED
@@ -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
|
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/
|
1
|
+
require 'rocketamf/pure/helpers/io_helper_read'
|
2
2
|
|
3
3
|
module RocketAMF
|
4
4
|
module Pure
|
5
|
-
# Pure ruby deserializer for
|
5
|
+
# Pure ruby deserializer for AMF3 requests
|
6
6
|
class Deserializer
|
7
|
-
|
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
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
105
|
-
read_int8(@source) != 0
|
106
|
-
end
|
41
|
+
requests = []
|
107
42
|
|
108
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
45
|
+
until @source.eof?
|
46
|
+
begin
|
47
|
+
@string_cache = []
|
48
|
+
@object_cache = []
|
49
|
+
@trait_cache = []
|
119
50
|
|
120
|
-
|
121
|
-
len = read_word32_network(@source)
|
122
|
-
result = []
|
123
|
-
@ref_cache << result
|
51
|
+
@position_request_read = @source.pos
|
124
52
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
result
|
129
|
-
end
|
53
|
+
requests << amf3_deserialize
|
54
|
+
rescue AMFErrorIncomplete => e
|
55
|
+
@source.pos = @position_request_read
|
130
56
|
|
131
|
-
|
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
|
-
|
139
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
amf0_read_props obj
|
63
|
+
{
|
64
|
+
requests: requests,
|
65
|
+
incomplete_request: incomplete_request
|
66
|
+
}
|
153
67
|
end
|
154
68
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
244
|
-
|
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
|
-
|
249
|
-
is_reference = (type & 0x01) == 0
|
181
|
+
result = nil
|
250
182
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
183
|
+
type = amf3_read_integer
|
184
|
+
|
185
|
+
result = get_as_reference_string(type)
|
186
|
+
|
187
|
+
if result.nil?
|
255
188
|
length = type >> 1
|
256
|
-
|
189
|
+
result = ''
|
190
|
+
|
257
191
|
if length > 0
|
258
|
-
|
259
|
-
|
260
|
-
|
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
|
270
|
-
is_reference = (type & 0x01) == 0
|
210
|
+
type = amf3_read_integer
|
271
211
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
else
|
212
|
+
result = get_as_reference_object(type)
|
213
|
+
|
214
|
+
if result.nil?
|
276
215
|
length = type >> 1
|
277
|
-
|
216
|
+
|
217
|
+
result = ''
|
218
|
+
|
278
219
|
if length > 0
|
279
|
-
|
280
|
-
|
281
|
-
|
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
|
-
|
291
|
-
is_reference = (type & 0x01) == 0
|
235
|
+
result = nil
|
292
236
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
237
|
+
type = amf3_read_integer
|
238
|
+
|
239
|
+
result = get_as_reference_object(type)
|
240
|
+
|
241
|
+
if result.nil?
|
297
242
|
length = type >> 1
|
298
|
-
|
299
|
-
@
|
300
|
-
|
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
|
-
|
306
|
-
is_reference = (type & 0x01) == 0
|
257
|
+
result = nil
|
307
258
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
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
|
-
|
315
|
-
@object_cache <<
|
266
|
+
result = property_name.length > 0 ? {} : []
|
267
|
+
@object_cache << result
|
316
268
|
|
317
269
|
while property_name.length > 0
|
318
|
-
value
|
319
|
-
|
320
|
-
property_name
|
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
|
-
|
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
|
-
|
330
|
-
is_reference = (type & 0x01) == 0
|
285
|
+
result = nil
|
331
286
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
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] ==
|
363
|
-
|
364
|
-
@object_cache <<
|
365
|
-
return
|
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
|
-
|
369
|
-
@object_cache <<
|
324
|
+
result = @class_mapper.get_ruby_obj(traits[:class_name])
|
325
|
+
@object_cache << result
|
370
326
|
|
371
327
|
if traits[:externalizable]
|
372
|
-
|
328
|
+
result.read_external(self)
|
373
329
|
else
|
374
|
-
|
330
|
+
properties = {}
|
331
|
+
|
375
332
|
traits[:members].each do |key|
|
376
|
-
value
|
377
|
-
|
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
|
385
|
-
|
339
|
+
value = amf3_deserialize
|
340
|
+
properties[key] = value
|
386
341
|
end
|
387
342
|
end
|
388
343
|
|
389
|
-
@class_mapper.populate_ruby_obj
|
344
|
+
@class_mapper.populate_ruby_obj(result, properties)
|
390
345
|
end
|
391
|
-
|
346
|
+
|
392
347
|
end
|
348
|
+
|
349
|
+
result
|
393
350
|
end
|
394
351
|
|
352
|
+
private
|
395
353
|
def amf3_read_date
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
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
|
-
|
404
|
-
@object_cache <<
|
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
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
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
|
381
|
+
weak_keys = read_int8(@source) # Ignore: Not supported in ruby
|
382
|
+
|
420
383
|
0.upto(length - 1) do |i|
|
421
|
-
|
384
|
+
result[amf3_deserialize] = amf3_deserialize
|
422
385
|
end
|
423
|
-
|
386
|
+
|
424
387
|
end
|
388
|
+
|
389
|
+
result
|
425
390
|
end
|
426
391
|
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
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
|
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
|
-
|
413
|
+
result << int
|
444
414
|
end
|
445
415
|
when AMF3_VECTOR_UINT_MARKER
|
446
416
|
0.upto(length - 1) do |i|
|
447
|
-
|
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
|
-
|
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
|
-
|
426
|
+
result << amf3_deserialize
|
459
427
|
end
|
428
|
+
else
|
429
|
+
#do nothing
|
460
430
|
end
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
431
|
+
end #if
|
432
|
+
|
433
|
+
result
|
434
|
+
end #read_vector
|
435
|
+
|
436
|
+
end #Deserializer
|
437
|
+
end #Pure
|
466
438
|
end
|