RocketAMF 0.0.5

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