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,94 @@
1
+ module RocketAMF
2
+ module Pure
3
+ module ReadIOHelpers #:nodoc:
4
+ def read_int8 source
5
+ source.read(1).unpack('c').first
6
+ end
7
+
8
+ def read_word8 source
9
+ source.read(1).unpack('C').first
10
+ end
11
+
12
+ def read_double source
13
+ source.read(8).unpack('G').first
14
+ end
15
+
16
+ def read_word16_network source
17
+ source.read(2).unpack('n').first
18
+ end
19
+
20
+ def read_int16_network source
21
+ str = source.read(2)
22
+ str.reverse! if byte_order_little? # swap bytes as native=little (and we want network)
23
+ str.unpack('s').first
24
+ end
25
+
26
+ def read_word32_network source
27
+ source.read(4).unpack('N').first
28
+ end
29
+
30
+ def byte_order
31
+ if [0x12345678].pack("L") == "\x12\x34\x56\x78"
32
+ :BigEndian
33
+ else
34
+ :LittleEndian
35
+ end
36
+ end
37
+
38
+ def byte_order_little?
39
+ (byte_order == :LittleEndian) ? true : false;
40
+ end
41
+ end
42
+
43
+ module WriteIOHelpers #:nodoc:
44
+ def pack_integer(integer)
45
+ integer = integer & 0x1fffffff
46
+ if(integer < 0x80)
47
+ [integer].pack('c')
48
+ elsif(integer < 0x4000)
49
+ [integer >> 7 & 0x7f | 0x80].pack('c')+
50
+ [integer & 0x7f].pack('c')
51
+ elsif(integer < 0x200000)
52
+ [integer >> 14 & 0x7f | 0x80].pack('c') +
53
+ [integer >> 7 & 0x7f | 0x80].pack('c') +
54
+ [integer & 0x7f].pack('c')
55
+ else
56
+ [integer >> 22 & 0x7f | 0x80].pack('c')+
57
+ [integer >> 15 & 0x7f | 0x80].pack('c')+
58
+ [integer >> 8 & 0x7f | 0x80].pack('c')+
59
+ [integer & 0xff].pack('c')
60
+ end
61
+ end
62
+
63
+ def pack_double(double)
64
+ [double].pack('G')
65
+ end
66
+
67
+ def pack_int8(val)
68
+ [val].pack('c')
69
+ end
70
+
71
+ def pack_int16_network(val)
72
+ [val].pack('n')
73
+ end
74
+
75
+ def pack_word32_network(val)
76
+ str = [val].pack('L')
77
+ str.reverse! if byte_order_little? # swap bytes as native=little (and we want network)
78
+ str
79
+ end
80
+
81
+ def byte_order
82
+ if [0x12345678].pack("L") == "\x12\x34\x56\x78"
83
+ :BigEndian
84
+ else
85
+ :LittleEndian
86
+ end
87
+ end
88
+
89
+ def byte_order_little?
90
+ (byte_order == :LittleEndian) ? true : false;
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,89 @@
1
+ require 'rocketamf/pure/io_helpers'
2
+
3
+ module RocketAMF
4
+ module Pure
5
+ # Request deserialization module - provides a method that can be included into
6
+ # RocketAMF::Request for deserializing the given stream.
7
+ module Request
8
+ def populate_from_stream stream
9
+ stream = StringIO.new(stream) unless StringIO === stream
10
+
11
+ # Initialize
12
+ @amf_version = 0
13
+ @headers = []
14
+ @messages = []
15
+
16
+ # Read AMF version
17
+ @amf_version = read_word16_network stream
18
+
19
+ # Read in headers
20
+ header_count = read_word16_network stream
21
+ 0.upto(header_count-1) do
22
+ name = stream.read(read_word16_network(stream))
23
+ must_understand = read_int8(stream) != 0
24
+ length = read_word32_network stream
25
+ data = RocketAMF.deserialize stream
26
+ @headers << RocketAMF::Header.new(name, must_understand, data)
27
+ end
28
+
29
+ # Read in messages
30
+ message_count = read_word16_network stream
31
+ 0.upto(message_count-1) do
32
+ target_uri = stream.read(read_word16_network(stream))
33
+ response_uri = stream.read(read_word16_network(stream))
34
+ length = read_word32_network stream
35
+ data = RocketAMF.deserialize stream
36
+ if data.is_a?(Array) && data.length == 1 && data[0].is_a?(::RocketAMF::Values::AbstractMessage)
37
+ data = data[0]
38
+ end
39
+ @messages << RocketAMF::Message.new(target_uri, response_uri, data)
40
+ end
41
+
42
+ self
43
+ end
44
+
45
+ private
46
+ include RocketAMF::Pure::ReadIOHelpers
47
+ end
48
+
49
+ # Response serialization module - provides a method that can be included into
50
+ # RocketAMF::Response for deserializing the given stream.
51
+ module Response
52
+ def serialize
53
+ stream = ""
54
+
55
+ # Write version
56
+ stream << pack_int16_network(@amf_version)
57
+
58
+ # Write headers
59
+ stream << pack_int16_network(@headers.length) # Header count
60
+ @headers.each do |h|
61
+ stream << pack_int16_network(h.name.length)
62
+ stream << h.name
63
+ stream << pack_int8(h.must_understand ? 1 : 0)
64
+ stream << pack_word32_network(-1)
65
+ stream << RocketAMF.serialize(h.data, 0)
66
+ end
67
+
68
+ # Write messages
69
+ stream << pack_int16_network(@messages.length) # Message count
70
+ @messages.each do |m|
71
+ stream << pack_int16_network(m.target_uri.length)
72
+ stream << m.target_uri
73
+
74
+ stream << pack_int16_network(m.response_uri.length)
75
+ stream << m.response_uri
76
+
77
+ stream << pack_word32_network(-1)
78
+ stream << AMF0_AMF3_MARKER if @amf_version == 3
79
+ stream << RocketAMF.serialize(m.data, @amf_version)
80
+ end
81
+
82
+ stream
83
+ end
84
+
85
+ private
86
+ include RocketAMF::Pure::WriteIOHelpers
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,327 @@
1
+ require 'rocketamf/pure/io_helpers'
2
+
3
+ module RocketAMF
4
+ module Pure
5
+ # AMF0 implementation of serializer
6
+ class Serializer
7
+ def initialize
8
+ @ref_cache = SerializerCache.new :object
9
+ end
10
+
11
+ def version
12
+ 0
13
+ end
14
+
15
+ def serialize obj, stream = ""
16
+ if obj.respond_to?(:to_amf)
17
+ stream << obj.to_amf(self)
18
+ elsif @ref_cache[obj] != nil
19
+ write_reference @ref_cache[obj], stream
20
+ elsif obj.is_a?(NilClass)
21
+ write_null stream
22
+ elsif obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
23
+ write_boolean obj, stream
24
+ elsif obj.is_a?(Float) || obj.is_a?(Integer)
25
+ write_number obj, stream
26
+ elsif obj.is_a?(Symbol) || obj.is_a?(String)
27
+ write_string obj.to_s, stream
28
+ elsif obj.is_a?(Time)
29
+ write_date obj, stream
30
+ elsif obj.is_a?(Array)
31
+ write_array obj, stream
32
+ elsif obj.is_a?(Hash)
33
+ write_hash obj, stream
34
+ elsif obj.is_a?(Object)
35
+ write_object obj, stream
36
+ end
37
+ stream
38
+ end
39
+
40
+ def write_null stream
41
+ stream << AMF0_NULL_MARKER
42
+ end
43
+
44
+ def write_boolean bool, stream
45
+ stream << AMF0_BOOLEAN_MARKER
46
+ stream << pack_int8(bool ? 1 : 0)
47
+ end
48
+
49
+ def write_number num, stream
50
+ stream << AMF0_NUMBER_MARKER
51
+ stream << pack_double(num)
52
+ end
53
+
54
+ def write_string str, stream
55
+ len = str.length
56
+ if len > 2**16-1
57
+ stream << AMF0_LONG_STRING_MARKER
58
+ stream << pack_word32_network(len)
59
+ else
60
+ stream << AMF0_STRING_MARKER
61
+ stream << pack_int16_network(len)
62
+ end
63
+ stream << str
64
+ end
65
+
66
+ def write_date date, stream
67
+ stream << AMF0_DATE_MARKER
68
+
69
+ date.utc unless date.utc?
70
+ seconds = (date.to_f * 1000).to_i
71
+ stream << pack_double(seconds)
72
+
73
+ stream << pack_int16_network(0)
74
+ end
75
+
76
+ def write_reference index, stream
77
+ stream << AMF0_REFERENCE_MARKER
78
+ stream << pack_int16_network(index)
79
+ end
80
+
81
+ def write_array array, stream
82
+ @ref_cache.add_obj array
83
+ stream << AMF0_STRICT_ARRAY_MARKER
84
+ stream << pack_word32_network(array.length)
85
+ array.each do |elem|
86
+ serialize elem, stream
87
+ end
88
+ end
89
+
90
+ def write_hash hash, stream
91
+ @ref_cache.add_obj hash
92
+ stream << AMF0_HASH_MARKER
93
+ stream << pack_word32_network(hash.length)
94
+ write_prop_list hash, stream
95
+ end
96
+
97
+ def write_object obj, stream
98
+ @ref_cache.add_obj obj
99
+
100
+ # Is it a typed object?
101
+ class_name = ClassMapper.get_as_class_name obj
102
+ if class_name
103
+ stream << AMF0_TYPED_OBJECT_MARKER
104
+ stream << pack_int16_network(class_name.length)
105
+ stream << class_name
106
+ else
107
+ stream << AMF0_OBJECT_MARKER
108
+ end
109
+
110
+ write_prop_list obj, stream
111
+ end
112
+
113
+ private
114
+ include RocketAMF::Pure::WriteIOHelpers
115
+ def write_prop_list obj, stream
116
+ # Write prop list
117
+ props = ClassMapper.props_for_serialization obj
118
+ props.sort.each do |key, value| # Sort keys before writing
119
+ stream << pack_int16_network(key.length)
120
+ stream << key
121
+ serialize value, stream
122
+ end
123
+
124
+ # Write end
125
+ stream << pack_int16_network(0)
126
+ stream << AMF0_OBJECT_END_MARKER
127
+ end
128
+ end
129
+
130
+ # AMF3 implementation of serializer
131
+ class AMF3Serializer
132
+ attr_reader :string_cache
133
+
134
+ def initialize
135
+ @string_cache = SerializerCache.new :string
136
+ @object_cache = SerializerCache.new :object
137
+ end
138
+
139
+ def version
140
+ 3
141
+ end
142
+
143
+ def serialize obj, stream = ""
144
+ if obj.respond_to?(:to_amf)
145
+ stream << obj.to_amf(self)
146
+ elsif obj.is_a?(NilClass)
147
+ write_null stream
148
+ elsif obj.is_a?(TrueClass)
149
+ write_true stream
150
+ elsif obj.is_a?(FalseClass)
151
+ write_false stream
152
+ elsif obj.is_a?(Float)
153
+ write_float obj, stream
154
+ elsif obj.is_a?(Integer)
155
+ write_integer obj, stream
156
+ elsif obj.is_a?(Symbol) || obj.is_a?(String)
157
+ write_string obj.to_s, stream
158
+ elsif obj.is_a?(Time)
159
+ write_date obj, stream
160
+ elsif obj.is_a?(Array)
161
+ write_array obj, stream
162
+ elsif obj.is_a?(Hash) || obj.is_a?(Object)
163
+ write_object obj, stream
164
+ end
165
+ stream
166
+ end
167
+
168
+ def write_reference index, stream
169
+ header = index << 1 # shift value left to leave a low bit of 0
170
+ stream << pack_integer(header)
171
+ end
172
+
173
+ def write_null stream
174
+ stream << AMF3_NULL_MARKER
175
+ end
176
+
177
+ def write_true stream
178
+ stream << AMF3_TRUE_MARKER
179
+ end
180
+
181
+ def write_false stream
182
+ stream << AMF3_FALSE_MARKER
183
+ end
184
+
185
+ def write_integer int, stream
186
+ if int < MIN_INTEGER || int > MAX_INTEGER # Check valid range for 29 bits
187
+ write_float int.to_f, stream
188
+ else
189
+ stream << AMF3_INTEGER_MARKER
190
+ stream << pack_integer(int)
191
+ end
192
+ end
193
+
194
+ def write_float float, stream
195
+ stream << AMF3_DOUBLE_MARKER
196
+ stream << pack_double(float)
197
+ end
198
+
199
+ def write_string str, stream
200
+ stream << AMF3_STRING_MARKER
201
+ write_utf8_vr str, stream
202
+ end
203
+
204
+ def write_date date, stream
205
+ stream << AMF3_DATE_MARKER
206
+ if @object_cache[date] != nil
207
+ write_reference @object_cache[date], stream
208
+ else
209
+ # Cache date
210
+ @object_cache.add_obj date
211
+
212
+ # Build AMF string
213
+ date.utc unless date.utc?
214
+ seconds = (date.to_f * 1000).to_i
215
+ stream << pack_integer(AMF3_NULL_MARKER)
216
+ stream << pack_double(seconds)
217
+ end
218
+ end
219
+
220
+ def write_array array, stream
221
+ stream << AMF3_ARRAY_MARKER
222
+ if @object_cache[array] != nil
223
+ write_reference @object_cache[array], stream
224
+ else
225
+ # Cache array
226
+ @object_cache.add_obj array
227
+
228
+ # Build AMF string
229
+ header = array.length << 1 # make room for a low bit of 1
230
+ header = header | 1 # set the low bit to 1
231
+ stream << pack_integer(header)
232
+ stream << AMF3_CLOSE_DYNAMIC_ARRAY
233
+ array.each do |elem|
234
+ serialize elem, stream
235
+ end
236
+ end
237
+ end
238
+
239
+ def write_object obj, stream
240
+ stream << AMF3_OBJECT_MARKER
241
+ if @object_cache[obj] != nil
242
+ write_reference @object_cache[obj], stream
243
+ else
244
+ # Cache object
245
+ @object_cache.add_obj obj
246
+
247
+ # Always serialize things as dynamic objects
248
+ stream << AMF3_DYNAMIC_OBJECT
249
+
250
+ # Write class name/anonymous
251
+ class_name = ClassMapper.get_as_class_name obj
252
+ if class_name
253
+ write_utf8_vr class_name, stream
254
+ else
255
+ stream << AMF3_ANONYMOUS_OBJECT
256
+ end
257
+
258
+ # Write out properties
259
+ props = ClassMapper.props_for_serialization obj
260
+ props.sort.each do |key, val| # Sort props until Ruby 1.9 becomes common
261
+ write_utf8_vr key.to_s, stream
262
+ serialize val, stream
263
+ end
264
+
265
+ # Write close
266
+ stream << AMF3_CLOSE_DYNAMIC_OBJECT
267
+ end
268
+ end
269
+
270
+ private
271
+ include RocketAMF::Pure::WriteIOHelpers
272
+
273
+ def write_utf8_vr str, stream
274
+ if str == ''
275
+ stream << AMF3_EMPTY_STRING
276
+ elsif @string_cache[str] != nil
277
+ write_reference @string_cache[str], stream
278
+ else
279
+ # Cache string
280
+ @string_cache.add_obj str
281
+
282
+ # Build AMF string
283
+ header = str.length << 1 # make room for a low bit of 1
284
+ header = header | 1 # set the low bit to 1
285
+ stream << pack_integer(header)
286
+ stream << str
287
+ end
288
+ end
289
+ end
290
+
291
+ class SerializerCache #:nodoc:
292
+ def self.new type
293
+ if type == :string
294
+ StringCache.new
295
+ elsif type == :object
296
+ ObjectCache.new
297
+ end
298
+ end
299
+
300
+ class StringCache < Hash #:nodoc:
301
+ def initialize
302
+ @cache_index = 0
303
+ end
304
+
305
+ def add_obj str
306
+ self[str] = @cache_index
307
+ @cache_index += 1
308
+ end
309
+ end
310
+
311
+ class ObjectCache < Hash #:nodoc:
312
+ def initialize
313
+ @cache_index = 0
314
+ end
315
+
316
+ def [] obj
317
+ super(obj.object_id)
318
+ end
319
+
320
+ def add_obj obj
321
+ self[obj.object_id] = @cache_index
322
+ @cache_index += 1
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end