amf-ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,354 @@
1
+ module AMF
2
+ class Deserializer
3
+ require 'stringio'
4
+ require 'ostruct'
5
+ require 'amf/constants'
6
+ require 'amf/header'
7
+ require 'amf/message'
8
+
9
+ attr_reader :headers, :messages, :version
10
+
11
+ def initialize( data )
12
+ if StringIO == data.class
13
+ @data = data
14
+ else
15
+ @data = StringIO.new data
16
+ end
17
+ readHeaders
18
+ readMessages
19
+
20
+ resetReferences
21
+ @data = nil
22
+ end
23
+
24
+ def serialize
25
+ AMF::Serializer.new( headers, messages, version ).data
26
+ end
27
+
28
+ # make this work a little bit more like a ruby object
29
+ def to_hash
30
+ {
31
+ 'version' => self.version,
32
+ 'headers' => self.headers.map{ |h| h.to_hash },
33
+ 'messages' => self.messages.map{ |m| m.to_hash },
34
+ }
35
+ end
36
+
37
+ private
38
+
39
+ def readHeaders
40
+ @headers = Array.new
41
+ if ! [0,3].include? readByte
42
+ raise "Data is not in expected format"
43
+ end
44
+
45
+ # throw away the next byte, flash version which we're ignoring
46
+ readByte
47
+
48
+ # we have Int headers, process them all
49
+ readInt.times do
50
+ resetReferences
51
+ name = readUTF
52
+ required = readByte == 1
53
+ length = readLong # throwaway
54
+ type = readByte
55
+ content = readData( type )
56
+
57
+ @headers << AMF::Header.new( name, required, content )
58
+ end
59
+ end
60
+
61
+ def readMessages
62
+ @messages = Array.new
63
+ readInt.times do
64
+ resetReferences
65
+ target = readUTF
66
+ response = readUTF
67
+ length = readLong # throwaway
68
+ type = readByte
69
+ data = readData( type )
70
+
71
+ @messages << AMF::Message.new( target, response, data )
72
+ end
73
+ end
74
+
75
+ def readData( type )
76
+ case type
77
+ when AMF0_AMF3_MARKER
78
+ readAMF3Data
79
+ when AMF0_NUMBER_MARKER
80
+ readDouble
81
+ when AMF0_BOOLEAN_MARKER
82
+ readByte == 1
83
+ when AMF0_STRING_MARKER
84
+ readUTF
85
+ when AMF0_OBJECT_MARKER
86
+ raise 'Unsupported type AMF0_OBJECT_MARKER'
87
+ when AMF0_MOVIE_CLIP_MARKER
88
+ raise 'Unsupported type AMF0_MOVIE_CLIP_MARKER'
89
+ when AMF0_NULL_MARKER
90
+ nil
91
+ when AMF0_UNDEFINED_MARKER
92
+ nil
93
+ when AMF0_REFERENCE_MARKER
94
+ raise 'Unsupported type AMF0_REFERENCE_MARKER'
95
+ when AMF0_HASH_MARKER
96
+ raise 'Unsupported type AMF0_HASH_MARKER'
97
+ when AMF0_OBJECT_END_MARKER
98
+ raise 'Unsupported type AMF0_OBJECT_END_MARKER'
99
+ when AMF0_STRICT_ARRAY_MARKER
100
+ raise 'Unsupported type AMF0_STRICT_ARRAY_MARKER'
101
+ when AMF0_DATE_MARKER
102
+ raise 'Unsupported type AMF0_DATE_MARKER'
103
+ when AMF0_LONG_STRING_MARKER
104
+ raise 'Unsupported type AMF0_LONG_STRING_MARKER'
105
+ when AMF0_UNSUPPORTED_MARKER
106
+ nil
107
+ when AMF0_RECORDSET_MARKER
108
+ raise 'Unsupported type AMF0_RECORDSET_MARKER'
109
+ when AMF0_XML_MARKER
110
+ raise 'Unsupported type AMF0_XML_MARKER'
111
+ when AMF0_TYPED_OBJECT_MARKER
112
+ raise 'Unsupported type AMF0_TYPED_OBJECT_MARKER'
113
+ else
114
+ raise "Unsupported AMF0 type: #{ type }"
115
+ end
116
+ end
117
+
118
+ def readAMF3Data
119
+ @version = AMF3_VERSION
120
+
121
+ type = readByte
122
+ case type
123
+ when AMF3_UNDEFINED_MARKER
124
+ nil
125
+ when AMF3_NULL_MARKER
126
+ nil
127
+ when AMF3_FALSE_MARKER
128
+ false
129
+ when AMF3_TRUE_MARKER
130
+ true
131
+ when AMF3_INTEGER_MARKER
132
+ readAMF3Int
133
+ when AMF3_DOUBLE_MARKER
134
+ readDouble
135
+ when AMF3_STRING_MARKER
136
+ readAMF3String
137
+ when AMF3_XML_DOC_MARKER
138
+ raise 'Unsupported type AMF3_XML_DOC_MARKER'
139
+ when AMF3_DATE_MARKER
140
+ readAMF3Date
141
+ when AMF3_ARRAY_MARKER
142
+ readAMF3Array
143
+ when AMF3_OBJECT_MARKER
144
+ readAMF3Object
145
+ when AMF3_XML_MARKER
146
+ raise 'Unsupported type AMF3_XML_MARKER'
147
+ when AMF3_BYTE_ARRAY_MARKER
148
+ raise 'Unsupported type AMF3_BYTE_ARRAY_MARKER'
149
+ when AMF3_DICT_MARKER
150
+ raise 'Unsupported type AMF3_DICT_MARKER'
151
+ else
152
+ raise "Unsported AMF3 type: #{ type }"
153
+ end
154
+ end
155
+
156
+ def readByte
157
+ @data.readchar.unpack("C")[0]
158
+ end
159
+
160
+ def readInt
161
+ ( readByte << 8 ) + readByte
162
+ end
163
+
164
+ def readUTF
165
+ length = readInt
166
+ if length == 0
167
+ ''
168
+ else
169
+ string = String.new
170
+ length.times do
171
+ string << readByte.chr
172
+ end
173
+ end
174
+ string
175
+ end
176
+
177
+ def readAMF3Int
178
+ int = readByte
179
+ if int < 128
180
+ int
181
+ else
182
+ int = ( int & 127 ) << 7
183
+ next_int = readByte
184
+ if next_int < 128
185
+ int | next_int
186
+ else
187
+ int = ( int | ( next_int & 127 ) ) << 7
188
+ next_int = readByte
189
+ if next_int < 128
190
+ int | next_int
191
+ else
192
+ int = ( int | ( next_int & 127 ) ) << 8
193
+ int |= readByte
194
+
195
+ # We have 29bit ints in AMF3, need to convert those to something
196
+ # more normalized
197
+ if int & 0x10000000 != 0
198
+ int |= ~0x1fffffff
199
+ end
200
+ int
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ def readDouble
207
+ bytes = String.new
208
+ 8.times do
209
+ bytes << readByte
210
+ end
211
+ bytes.to_s.reverse.unpack( 'dbfl' )[0]
212
+ end
213
+
214
+ def readLong
215
+ ( readByte << 24 ) | ( readByte << 16 ) | ( readByte << 8 ) | readByte
216
+ end
217
+
218
+ def readAMF3String
219
+ strref = readAMF3Int
220
+
221
+ if ( strref & 1 ) == 0
222
+ strref = strref >> 1
223
+ if @storedStrings[ strref ].nil?
224
+ raise "found a undefined string ref: #{ strref }"
225
+ end
226
+ @storedStrings[ strref ]
227
+ else
228
+ strlen = strref >> 1
229
+ str = String.new
230
+ if strlen > 0
231
+ @storedStrings << readBuffer( strlen )
232
+ @storedStrings.last
233
+ else
234
+ ''
235
+ end
236
+ end
237
+ end
238
+
239
+ def readBuffer( length )
240
+ data = String.new
241
+ length.times do
242
+ data << readByte.chr
243
+ end
244
+ data
245
+ end
246
+
247
+ def readAMF3Date
248
+ firstInt = readAMF3Int
249
+ if ( firstInt & 1 ) == 0
250
+ firstInt = firstInt >> 1
251
+ if @storedObjects[ firstInt ].nil?
252
+ raise "found an undeifned storedObject ref: #{ firstInt }"
253
+ end
254
+ @storedObjects[ firstInt ]
255
+ else
256
+ ms = readDouble
257
+ @storedObjects << Time.at( ms )
258
+ Time.at ms
259
+ end
260
+ end
261
+
262
+ def readAMF3Array
263
+ handle = readAMF3Int
264
+ inline = ( handle & 1 ) != 0
265
+ handle = handle >> 1
266
+ if inline
267
+ storeable = Hash.new
268
+ @storedObjects << storeable
269
+ key = readAMF3String
270
+ while key != ''
271
+ storeable[ key ] = readAMF3Data
272
+ key = readAMF3String
273
+ end
274
+
275
+ handle.times do |i|
276
+ storeable[ i ] = readAMF3Data
277
+ end
278
+
279
+ storeable
280
+ else
281
+ @storedObjects[ handle ]
282
+ end
283
+ end
284
+
285
+ # uhhggg, this one sucks!
286
+ def readAMF3Object
287
+ handle = readAMF3Int
288
+ inline = ( handle & 1 ) != 0
289
+ handle = handle >> 1
290
+
291
+ if ! inline
292
+ return @storedObjects[ handle ]
293
+ end
294
+
295
+ inlineClassDef = ( handle &1 ) != 0
296
+ handle = handle >> 1
297
+ if inlineClassDef
298
+ typeId = readAMF3String
299
+ externalizable = ( handle &1 ) != 0
300
+ handle = handle >> 1
301
+ dynamic = ( handle &1 ) !=0
302
+ handle = handle >> 1
303
+ classMemberCount = handle
304
+
305
+ classMemberDefinitions = Array.new
306
+ classMemberCount.times do
307
+ classMemberDefinitions << readAMF3String
308
+ end
309
+
310
+ classDefinition = {
311
+ 'type' => typeId,
312
+ 'externalizable' => externalizable,
313
+ 'dynamic' => dynamic,
314
+ 'members' => classMemberDefinitions
315
+ }
316
+ @storedDefinitions << classDefinition
317
+ else
318
+ classDefinition = @storedDefinitions[ handle ]
319
+ end
320
+
321
+ obj = OpenStruct.new
322
+ @storedObjects << obj
323
+
324
+ if classDefinition[ 'externalizable' ]
325
+ obj.send( "#{ AMF_FIELD_EXTERNALIZED_DATA }=", readAMF3Data )
326
+ else
327
+ classDefinition[ 'members' ].each do |member|
328
+ obj.send( "#{ member }=", readAMF3Data )
329
+ end
330
+
331
+ if classDefinition[ 'dynamic' ]
332
+ key = readAMF3String
333
+ while key != ''
334
+ obj.send( "#{ key }=", readAMF3Data )
335
+ key = readAMF3String
336
+ end
337
+ end
338
+ end
339
+
340
+ if classDefinition[ 'type' ] != ''
341
+ obj.send( "#{ AMF_FIELD_EXPLICIT_TYPE }=", classDefinition[ 'type' ] )
342
+ end
343
+
344
+ obj
345
+ end
346
+
347
+ def resetReferences
348
+ @storedStrings = Array.new
349
+ @storedObjects = Array.new
350
+ @storedDefinitions = Array.new
351
+ end
352
+ end
353
+ end
354
+
@@ -0,0 +1,21 @@
1
+ module AMF
2
+ class Header
3
+ attr_accessor :target, :required, :data
4
+
5
+ def initialize( target, required, data )
6
+ @target = target
7
+ @required = required
8
+ @data = data
9
+ end
10
+
11
+ def to_hash
12
+ { 'name' => self.target, 'required' => self.required, 'data' => self.data }
13
+ end
14
+
15
+ # needed this method, it is a bit of a hack
16
+ def each
17
+ self.to_hash.each
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,36 @@
1
+ module AMF
2
+ class Message
3
+ attr_accessor :targetURL, :responseURL, :data
4
+
5
+ def initialize( targetURL, responseURL, data )
6
+ @targetURL = targetURL
7
+ @responseURL = responseURL
8
+ @data = data
9
+ end
10
+
11
+ def to_hash
12
+ { 'targetUri' => self.targetURL, 'responseUri' => self.responseURL, 'data' => recursive_marshal( data ) }
13
+ end
14
+
15
+ private
16
+
17
+ def recursive_marshal( data )
18
+ ret = Hash.new
19
+ if data.class == OpenStruct
20
+ data.marshal_dump.each{ |k,v|
21
+ ret[k] = recursive_marshal v
22
+ }
23
+ elsif data.class == Hash
24
+ data.each{ |k,v|
25
+ ret[k] = recursive_marshal v
26
+ }
27
+ else
28
+ ret = data
29
+ end
30
+
31
+ ret
32
+ end
33
+
34
+ end
35
+ end
36
+
@@ -0,0 +1,288 @@
1
+ module AMF
2
+ class Serializer
3
+
4
+ attr_accessor :data
5
+
6
+ def initialize( headers, messages, amfVersion )
7
+ @headers = headers
8
+ @messages = messages
9
+ @amfVersion = amfVersion
10
+ @data = String.new
11
+
12
+ writeInt 0 # start if off right
13
+
14
+ # start writing the headers
15
+ writeInt @headers.count
16
+ @headers.each do |header|
17
+ resetReferences
18
+ writeUTF header.target
19
+ if header.required == true
20
+ writeByte 1
21
+ else
22
+ writeByte 0
23
+ end
24
+ tmpdata = @data
25
+ @data = String.new
26
+ writeData header.data
27
+ serializedHeader = @data
28
+ @data = tmpdata
29
+ writeLong serializedHeader.length
30
+ @data += serializedHeader
31
+ end
32
+
33
+ # and write the data
34
+ writeInt @messages.count
35
+ @messages.each do |message|
36
+ resetReferences
37
+ writeUTF message.targetURL
38
+ writeUTF message.responseURL
39
+ tmpdata = @data
40
+ @data = String.new
41
+ writeData message.data
42
+ serializedMessage = @data
43
+ @data = tmpdata
44
+ writeLong serializedMessage.length
45
+ @data += serializedMessage
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def writeData( data )
52
+ if @amfVersion == AMF3_VERSION
53
+ writeByte( AMF0_AMF3_MARKER )
54
+ writeAMF3Data( data )
55
+ else
56
+ raise "Unsupported writing AMF0 types"
57
+ end
58
+ end
59
+
60
+ def writeAMF3Data( data )
61
+ case data.class.to_s
62
+ when 'Fixnum'
63
+ writeAMF3Number( data )
64
+ when 'Float'
65
+ writeByte( AMF3_DOUBLE_MARKER )
66
+ writeDouble( data )
67
+ when 'String'
68
+ writeByte( AMF3_STRING_MARKER )
69
+ writeAMF3String( data )
70
+ when 'FalseClass'
71
+ writeByte( AMF3_FALSE_MARKER )
72
+ when 'TrueClass'
73
+ writeByte( AMF3_TRUE_MARKER )
74
+ when 'NilClass'
75
+ writeByte( AMF3_NULL_MARKER )
76
+ when 'Time'
77
+ writeAMF3Date( data )
78
+ when 'Hash'
79
+ writeAMF3Array( data )
80
+ when 'OpenStruct'
81
+ if data.send( AMF_FIELD_EXPLICIT_TYPE )
82
+ writeAMF3TypedObject( data )
83
+ else
84
+ writeAMF3AnonymousObject( data )
85
+ end
86
+ else
87
+ raise "Unknown data type: #{ data.class }"
88
+ end
89
+ end
90
+
91
+ def writeByte( byte )
92
+ @data << [ byte ].pack( 'c' )
93
+ end
94
+
95
+ def writeInt( int )
96
+ @data << [ int ].pack( 'n' )
97
+ end
98
+
99
+ def writeLong( long )
100
+ @data << [ long ].pack( 'N' )
101
+ end
102
+
103
+ def writeDouble( double )
104
+ @data << [ double ].pack( 'd' ).reverse
105
+ end
106
+
107
+ def writeUTF( string )
108
+ writeInt( string.length )
109
+ @data << string
110
+ end
111
+
112
+ def writeLongUTF( string )
113
+ writeLong( string.length )
114
+ @data << string
115
+ end
116
+
117
+ def writeBoolean( bit )
118
+ writeByte( AMF0_BOOLEAN_MARKER )
119
+ writeByte( bit )
120
+ end
121
+
122
+ def writeString( string )
123
+ if string.count < 65536
124
+ writeByte( AMF0_STRING_MARKER )
125
+ writeUFT( string )
126
+ else
127
+ writeByte( AMF0_LONG_STRING_MARKER )
128
+ writeLongUTF( string )
129
+ end
130
+ end
131
+
132
+ def writeNumber( number )
133
+ writeByte( AMF0_NUMBER_MARKER )
134
+ writeDouble( number.to_f )
135
+ end
136
+
137
+ def writeNull
138
+ writeByte( AMF0_NULL_MARKER )
139
+ end
140
+
141
+ def writeUndefined
142
+ writeByte( AMF0_UNDEFINED_MARKER )
143
+ end
144
+
145
+ def writeAMF3Int( int )
146
+ int &= 0x1fffffff
147
+ if int < 0x80
148
+ data = int.chr
149
+ elsif int < 0x4000
150
+ data = ( int >> 7 & 0x7f | 0x80 ).chr + ( int & 0x7f ).chr
151
+ elsif int < 0x200000
152
+ data = ( int >> 14 & 0x7f | 0x80 ).chr + ( int >> 7 & 0x7f | 0x80 ).chr + ( int & 0x7f ).chr
153
+ else
154
+ data = ( int >> 22 & 0x7f | 0x80 ).chr + ( int >> 15 & 0x7f | 0x80 ).chr + ( int >> 8 & 0x7f | 0x80 ).chr + ( int & 0xff ).chr
155
+ end
156
+
157
+ @data += data
158
+ end
159
+
160
+ def writeAMF3String( string )
161
+ if string.empty?
162
+ writeByte( AMF3_NULL_MARKER )
163
+ elsif ! handleReference( string, @storedStrings )
164
+ writeAMF3Int( ( string.length << 1 | 1 ) )
165
+ @data += string
166
+ end
167
+ end
168
+
169
+ # in ruby speak this is time, but we go with the status quo
170
+ def writeAMF3Date( date )
171
+ writeByte AMF3_DATE_MARKER
172
+ writeAMF3Int 1
173
+ writeDouble date.to_f
174
+ end
175
+
176
+ def writeAMF3Array( array )
177
+ if @storedObjects.keys.count <= AMF_MAX_STORED_OBJECTS
178
+ @storedObjects[ @storedObjects.keys.count ] = @storedObjects.keys.count
179
+ end
180
+
181
+ # meh, so there is a whole boat of stuff that we're missing here -
182
+ # arrays that are sparse, arrays with string 'keys'. in my testing
183
+ # I never saw any of those data types so just doing it the easy way
184
+ writeByte( AMF3_ARRAY_MARKER )
185
+ writeAMF3Int( ( array.count * 2 ) + 1 )
186
+ array.select{ |x| x.class == String }.each do |key, value|
187
+ writeAMF3String( key.to_s )
188
+ writeAMF3Data( value )
189
+ end
190
+ writeAMF3String('')
191
+
192
+ array.select{ |x| x.class == Fixnum }.each do |key,value|
193
+ writeAMF3Data value
194
+ end
195
+ end
196
+
197
+ def writeAMF3TypedObject( data )
198
+ writeByte( AMF3_OBJECT_MARKER )
199
+ if ! handleReference( data, @storedObjects )
200
+ classname = data.send( AMF_FIELD_EXPLICIT_TYPE )
201
+ if @className2TraitsInfo[ classname ].nil?
202
+ propertyNames = Array.new
203
+ data.marshal_dump.each do |key, value|
204
+ if key[0] != "\0" and key.to_s != AMF_FIELD_EXPLICIT_TYPE
205
+ propertyNames << key
206
+ end
207
+ end
208
+
209
+ writeAMF3Int( propertyNames.count << 4 | 3 )
210
+ writeAMF3String( classname )
211
+ propertyNames.each do |p|
212
+ writeAMF3String( p.to_s )
213
+ end
214
+
215
+ traitsInfo = { 'referenceId' => @className2TraitsInfo.keys.count, 'propertyNames' => propertyNames }
216
+ @className2TraitsInfo[ classname ] = traitsInfo
217
+ else
218
+ traitsInfo = @className2TraitsInfo[ classname ]
219
+ referenceId = traitsInfo[ 'referenceId' ]
220
+ propertyNames = traitsInfo[ 'propertyNames' ]
221
+ writeAMF3Int( referenceId << 2 | 1 )
222
+ end
223
+
224
+ propertyNames.each do |p|
225
+ writeAMF3Data( data.marshal_dump[ p ] )
226
+ end
227
+ end
228
+ end
229
+
230
+
231
+ def writeAMF3AnonymousObject( data, doRef = true )
232
+ writeByte( AMF3_OBJECT_MARKER )
233
+ if doRef && handleReference( data, @storedObjects )
234
+ return
235
+ end
236
+
237
+ writeAMF3Int( 0xB )
238
+ @className2TraitsInfo[ data.hash ] = Hash.new
239
+ writeAMF3String( '' )
240
+ data.marshal_dump.each do |key, value|
241
+ writeAMF3String( key.to_s )
242
+ writeAMF3Data( value )
243
+ end
244
+ writeByte( AMF3_NULL_MARKER )
245
+ end
246
+
247
+ def writeAMF3Number( number )
248
+ # can only handle signed 29bit ints
249
+ if number >= -2^28 or number <= 2^28
250
+ writeByte( AMF3_INTEGER_MARKER )
251
+ writeAMF3Int( number )
252
+ else
253
+ writeByte( AMF3_DOUBLE_MARKER )
254
+ writeDouble( number )
255
+ end
256
+ end
257
+
258
+ def handleReference( obj, reference )
259
+ key = false
260
+ hash = obj.hash.to_s
261
+ if reference[ hash ].nil?
262
+ if reference.keys.count <= AMF_MAX_STORED_OBJECTS
263
+ reference[ hash ] = reference.keys.count
264
+ end
265
+ else
266
+ key = reference[ hash ]
267
+ end
268
+
269
+ if key
270
+ if @amfVersion == AMF0_VERSION
271
+ raise "unsupported AMF0_VERSION reference"
272
+ else
273
+ handle = key << 1
274
+ writeAMF3Int( handle )
275
+ return true
276
+ end
277
+ else
278
+ return false
279
+ end
280
+ end
281
+
282
+ def resetReferences
283
+ @storedObjects = Hash.new
284
+ @storedStrings = Hash.new
285
+ @className2TraitsInfo = Hash.new
286
+ end
287
+ end
288
+ end