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.
- checksums.yaml +7 -0
- data/.autotest +25 -0
- data/.hoeignore +10 -0
- data/Gemfile +18 -0
- data/History.txt +6 -0
- data/LICENSE +202 -0
- data/Manifest.txt +17 -0
- data/README.md +37 -0
- data/Rakefile +62 -0
- data/bin/amf +3 -0
- data/lib/amf.rb +6 -0
- data/lib/amf/constants.rb +48 -0
- data/lib/amf/deserializer.rb +354 -0
- data/lib/amf/header.rb +21 -0
- data/lib/amf/message.rb +36 -0
- data/lib/amf/serializer.rb +288 -0
- data/lib/amf/version.rb +3 -0
- data/test/test_amf.rb +9 -0
- metadata +196 -0
@@ -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
|
+
|
data/lib/amf/header.rb
ADDED
@@ -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
|
+
|
data/lib/amf/message.rb
ADDED
@@ -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
|