amfora 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,145 @@
1
+ module AMF
2
+ module Messages #:nodoc:
3
+ # Base class for all special AS3 response messages. Maps to
4
+ # <tt>flex.messaging.messages.AbstractMessage</tt>
5
+ class AbstractMessage
6
+ attr_accessor :client_id
7
+ attr_accessor :destination
8
+ attr_accessor :message_id
9
+ attr_accessor :timestamp
10
+ attr_accessor :time_to_live
11
+ attr_accessor :headers
12
+ attr_accessor :body
13
+
14
+ def to_amf(options = {})
15
+ options[:amf_version] ||= 3
16
+ options[:serializable_names] = (self.public_methods - Object.new.public_methods).delete_if { |elm| elm =~ /to_amf|body|.*=/ }
17
+
18
+ AMF.serialize(self, options) do |s|
19
+ if body
20
+ s.write_utf8_vr("body")
21
+ s.stream << body
22
+ end
23
+ end
24
+ end
25
+
26
+ protected
27
+ def rand_uuid
28
+ [8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-').to_s
29
+ end
30
+
31
+ def rand_hex_3(l)
32
+ "%0#{l}x" % rand(1 << l*4)
33
+ end
34
+ end
35
+
36
+ # Maps to <tt>flex.messaging.messages.RemotingMessage</tt>
37
+ class RemotingMessage < AbstractMessage
38
+ # The name of the service to be called including package name
39
+ attr_accessor :source
40
+
41
+ # The name of the method to be called
42
+ attr_accessor :operation
43
+
44
+ # The arguments to call the method with
45
+ attr_accessor :parameters
46
+
47
+ def initialize
48
+ @client_id = rand_uuid
49
+ @destination = nil
50
+ @message_id = rand_uuid
51
+ @timestamp = Time.new.to_i*100
52
+ @time_to_live = 0
53
+ @headers = {}
54
+ @body = nil
55
+ end
56
+ end
57
+
58
+ # Maps to <tt>flex.messaging.messages.AsyncMessage</tt>
59
+ class AsyncMessage < AbstractMessage
60
+ attr_accessor :correlation_id
61
+ end
62
+
63
+ # Maps to <tt>flex.messaging.messages.CommandMessage</tt>
64
+ class CommandMessage < AsyncMessage
65
+ SUBSCRIBE_OPERATION = 0
66
+ UNSUSBSCRIBE_OPERATION = 1
67
+ POLL_OPERATION = 2
68
+ CLIENT_SYNC_OPERATION = 4
69
+ CLIENT_PING_OPERATION = 5
70
+ CLUSTER_REQUEST_OPERATION = 7
71
+ LOGIN_OPERATION = 8
72
+ LOGOUT_OPERATION = 9
73
+ SESSION_INVALIDATE_OPERATION = 10
74
+ MULTI_SUBSCRIBE_OPERATION = 11
75
+ DISCONNECT_OPERATION = 12
76
+ UNKNOWN_OPERATION = 10000
77
+
78
+ attr_accessor :operation
79
+
80
+ def initialize
81
+ @operation = UNKNOWN_OPERATION
82
+ end
83
+ end
84
+
85
+ # Maps to <tt>flex.messaging.messages.AcknowledgeMessage</tt>
86
+ class AcknowledgeMessage < AsyncMessage
87
+ def initialize(message = nil)
88
+ @client_id = rand_uuid
89
+ @destination = nil
90
+ @message_id = rand_uuid
91
+ @timestamp = Time.new.to_i*100
92
+ @time_to_live = 0
93
+ @headers = {}
94
+ @body = nil
95
+
96
+ if message.is_a?(AbstractMessage)
97
+ @correlation_id = message.message_id
98
+ end
99
+ end
100
+ end
101
+
102
+ # Maps to <tt>flex.messaging.messages.ErrorMessage</tt> in AMF3 mode
103
+ class ErrorMessage < AcknowledgeMessage
104
+ # Extended data that will facilitate custom error processing on the client
105
+ attr_accessor :extended_data
106
+
107
+ # The fault code for the error, which defaults to the class name of the
108
+ # causing exception
109
+ attr_accessor :fault_code
110
+
111
+ # Detailed description of what caused the error
112
+ attr_accessor :fault_detail
113
+
114
+ # A simple description of the error
115
+ attr_accessor :fault_string
116
+
117
+ # Optional "root cause" of the error
118
+ attr_accessor :root_cause
119
+
120
+ def initialize message, exception
121
+ super message
122
+
123
+ @e = exception
124
+ @fault_code = @e.class.name
125
+ @fault_detail = @e.backtrace.join("\n")
126
+ @fault_string = @e.message
127
+ end
128
+
129
+ def to_amf serializer
130
+ stream = ""
131
+ # if serializer.version == 0
132
+ # data = {
133
+ # :faultCode => @faultCode,
134
+ # :faultDetail => @faultDetail,
135
+ # :faultString => @faultString
136
+ # }
137
+ # serializer.write_hash(data, stream)
138
+ # else
139
+ # serializer.write_object(self, stream)
140
+ # end
141
+ stream
142
+ end
143
+ end
144
+ end
145
+ end
data/lib/amf/pure.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'amf/constants'
2
+ require 'amf/pure/serializer'
3
+ require 'amf/pure/deserializer'
4
+ require 'amf/pure/remoting'
5
+
6
+ module AMF
7
+ # This module holds all the modules/classes that implement AMF's
8
+ # functionality in pure ruby.
9
+ module Pure
10
+ $DEBUG and warn "Using pure library for AMF."
11
+ end
12
+
13
+ include AMF::Pure
14
+ end
@@ -0,0 +1,362 @@
1
+ require 'amf/pure/io_helpers'
2
+
3
+ module AMF
4
+ module Pure
5
+ # Pure ruby deserializer
6
+ #--
7
+ # AMF0 deserializer, it switches over to AMF3 when it sees the switch flag
8
+ class AMF0Deserializer
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 AMF::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).underscore
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).underscore
89
+
90
+ type = read_int8(source)
91
+ return [] if type == AMF0_OBJECT_END_MARKER
92
+
93
+ # We need to figure out whether this is a real hash, or whether some stupid serializer gave up
94
+ if key.to_i.to_s == key
95
+ # Array
96
+ obj = []
97
+ @ref_cache << obj
98
+
99
+ obj[key.to_i] = deserialize(source, type)
100
+ while true
101
+ key = read_string(source).underscore
102
+ type = read_int8(source)
103
+ break if type == AMF0_OBJECT_END_MARKER
104
+ obj[key.to_i] = deserialize(source, type)
105
+ end
106
+ else
107
+ # Hash
108
+ obj = {}
109
+ @ref_cache << obj
110
+
111
+ obj[key.to_sym] = deserialize(source, type)
112
+ while true
113
+ key = read_string(source).underscore
114
+ type = read_int8(source)
115
+ break if type == AMF0_OBJECT_END_MARKER
116
+ obj[key.to_sym] = deserialize(source, type)
117
+ end
118
+ end
119
+ obj
120
+ end
121
+
122
+ def read_array(source)
123
+ len = read_word32_network(source)
124
+ array = []
125
+ @ref_cache << array
126
+
127
+ 0.upto(len - 1) do
128
+ array << deserialize(source)
129
+ end
130
+ array
131
+ end
132
+
133
+ def read_date(source)
134
+ seconds = read_double(source).to_f/1000
135
+ time = Time.at(seconds)
136
+ tz = read_word16_network(source) # Unused
137
+ time
138
+ end
139
+
140
+ def read_typed_object(source)
141
+ # Create object to add to ref cache
142
+ class_name = read_string(source)
143
+ obj = ClassMapper.get_ruby_obj(class_name)
144
+ @ref_cache << obj
145
+
146
+ # Read object props
147
+ props = read_object(source, false)
148
+
149
+ # Populate object
150
+ ClassMapper.populate_ruby_obj(obj, props, {})
151
+ return obj
152
+ end
153
+ end
154
+
155
+ # AMF3 implementation of deserializer, loaded automatically by the AMF0
156
+ # deserializer when needed
157
+ class AMF3Deserializer
158
+ def initialize
159
+ @string_cache = []
160
+ @object_cache = []
161
+ @trait_cache = []
162
+ end
163
+
164
+ def deserialize(source, type=nil)
165
+ source = StringIO.new(source) unless StringIO === source
166
+ type = read_int8 source unless type
167
+ case type
168
+ when AMF3_UNDEFINED_MARKER
169
+ nil
170
+ when AMF3_NULL_MARKER
171
+ nil
172
+ when AMF3_FALSE_MARKER
173
+ false
174
+ when AMF3_TRUE_MARKER
175
+ true
176
+ when AMF3_INTEGER_MARKER
177
+ read_integer(source)
178
+ when AMF3_DOUBLE_MARKER
179
+ read_number(source)
180
+ when AMF3_STRING_MARKER
181
+ read_string(source)
182
+ when AMF3_XML_DOC_MARKER
183
+ #read_xml_string
184
+ when AMF3_DATE_MARKER
185
+ read_date(source)
186
+ when AMF3_ARRAY_MARKER
187
+ read_array(source)
188
+ when AMF3_OBJECT_MARKER
189
+ read_object(source)
190
+ when AMF3_XML_MARKER
191
+ #read_amf3_xml
192
+ when AMF3_BYTE_ARRAY_MARKER
193
+ #read_amf3_byte_array
194
+ end
195
+ end
196
+
197
+ private
198
+ include AMF::Pure::ReadIOHelpers
199
+
200
+ def read_integer(source)
201
+ n = 0
202
+ b = read_word8(source) || 0
203
+ result = 0
204
+
205
+ while ((b & 0x80) != 0 && n < 3)
206
+ result = result << 7
207
+ result = result | (b & 0x7f)
208
+ b = read_word8(source) || 0
209
+ n = n + 1
210
+ end
211
+
212
+ if (n < 3)
213
+ result = result << 7
214
+ result = result | b
215
+ else
216
+ #Use all 8 bits from the 4th byte
217
+ result = result << 8
218
+ result = result | b
219
+
220
+ #Check if the integer should be negative
221
+ if (result > MAX_INTEGER)
222
+ result -= (1 << 29)
223
+ end
224
+ end
225
+ result
226
+ end
227
+
228
+ def read_number(source)
229
+ res = read_double(source)
230
+ res.is_a?(Float)&&res.nan? ? nil : res # check for NaN and convert them to nil
231
+ end
232
+
233
+ def read_string(source)
234
+ type = read_integer(source)
235
+ is_reference = (type & 0x01) == 0
236
+
237
+ if is_reference
238
+ reference = type >> 1
239
+ return @string_cache[reference]
240
+ else
241
+ length = type >> 1
242
+ #HACK needed for ['',''] array of empty strings
243
+ #It may be better to take one more parameter that
244
+ #would specify whether or not they expect us to return
245
+ #a string
246
+ str = "" #if stringRequest
247
+ if length > 0
248
+ str = source.read(length)
249
+ @string_cache << str
250
+ end
251
+ return str
252
+ end
253
+ end
254
+
255
+ def read_array(source)
256
+ type = read_integer(source)
257
+ is_reference = (type & 0x01) == 0
258
+
259
+ if is_reference
260
+ reference = type >> 1
261
+ return @object_cache[reference]
262
+ else
263
+ length = type >> 1
264
+ property_name = read_string(source).underscore
265
+ if property_name != ""
266
+ array = {}
267
+ @object_cache << array
268
+ begin
269
+ while(property_name.length)
270
+ value = deserialize(source)
271
+ array[property_name] = value
272
+ property_name = read_string(source)
273
+ end
274
+ rescue Exception => e #end of object exception, because property_name.length will be non existent
275
+ end
276
+ 0.upto(length - 1) do |i|
277
+ array["" + i.to_s] = deserialize(source)
278
+ end
279
+ else
280
+ array = []
281
+ @object_cache << array
282
+ 0.upto(length - 1) do
283
+ array << deserialize(source)
284
+ end
285
+ end
286
+ array
287
+ end
288
+ end
289
+
290
+ def read_object(source)
291
+ type = read_integer(source)
292
+ is_reference = (type & 0x01) == 0
293
+
294
+ if is_reference
295
+ reference = type >> 1
296
+ return @object_cache[reference]
297
+ else
298
+ class_type = type >> 1
299
+ class_is_reference = (class_type & 0x01) == 0
300
+
301
+ if class_is_reference
302
+ reference = class_type >> 1
303
+ class_definition = @trait_cache[reference]
304
+ else
305
+ class_name = read_string(source)
306
+ externalizable = (class_type & 0x02) != 0
307
+ dynamic = (class_type & 0x04) != 0
308
+ attribute_count = class_type >> 3
309
+
310
+ class_attributes = []
311
+ attribute_count.times{class_attributes << read_string(source)} # Read class members
312
+
313
+ class_definition = {"class_name" => class_name,
314
+ "members" => class_attributes,
315
+ "externalizable" => externalizable,
316
+ "dynamic" => dynamic}
317
+ @trait_cache << class_definition
318
+ end
319
+
320
+ obj = ClassMapper.get_ruby_obj(class_definition["class_name"])
321
+ @object_cache << obj
322
+
323
+ if class_definition['externalizable']
324
+ obj.externalized_data = deserialize(source)
325
+ else
326
+ props = {}
327
+ class_definition['members'].each do |key|
328
+ value = deserialize(source)
329
+ props[key.underscore.to_sym] = value
330
+ end
331
+
332
+ dynamic_props = nil
333
+ if class_definition['dynamic']
334
+ dynamic_props = {}
335
+ while (key = read_string(source).underscore) && key.length != 0 do # read next key
336
+ value = deserialize(source)
337
+ dynamic_props[key.to_sym] = value
338
+ end
339
+ end
340
+
341
+ ClassMapper.populate_ruby_obj(obj, props, dynamic_props)
342
+ end
343
+ obj
344
+ end
345
+ end
346
+
347
+ def read_date(source)
348
+ type = read_integer(source)
349
+ is_reference = (type & 0x01) == 0
350
+ if is_reference
351
+ reference = type >> 1
352
+ return @object_cache[reference]
353
+ else
354
+ seconds = read_double(source).to_f/1000
355
+ time = Time.at(seconds)
356
+ @object_cache << time
357
+ time
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end