amf-ruby 0.0.2

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,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