amfora 0.0.1

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.
@@ -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