ruby_sol 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.
- checksums.yaml +7 -0
- data/README.rdoc +90 -0
- data/Rakefile +43 -0
- data/lib/ruby_sol.rb +113 -0
- data/lib/ruby_sol/class_mapping.rb +250 -0
- data/lib/ruby_sol/constants.rb +50 -0
- data/lib/ruby_sol/extensions.rb +22 -0
- data/lib/ruby_sol/pure/deserializer.rb +461 -0
- data/lib/ruby_sol/pure/io_helpers.rb +94 -0
- data/lib/ruby_sol/pure/serializer.rb +490 -0
- data/spec/class_mapping_spec.rb +110 -0
- data/spec/deserializer_spec.rb +449 -0
- data/spec/fixtures/objects/amf0-boolean.bin +1 -0
- data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
- data/spec/fixtures/objects/amf0-date.bin +0 -0
- data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
- data/spec/fixtures/objects/amf0-hash.bin +0 -0
- data/spec/fixtures/objects/amf0-null.bin +1 -0
- data/spec/fixtures/objects/amf0-number.bin +0 -0
- data/spec/fixtures/objects/amf0-object.bin +0 -0
- data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
- data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
- data/spec/fixtures/objects/amf0-string.bin +0 -0
- data/spec/fixtures/objects/amf0-time.bin +0 -0
- data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
- data/spec/fixtures/objects/amf0-undefined.bin +1 -0
- data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
- data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
- data/spec/fixtures/objects/amf3-0.bin +0 -0
- data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
- data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
- data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
- data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
- data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
- data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-date.bin +0 -0
- data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
- data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
- data/spec/fixtures/objects/amf3-false.bin +1 -0
- data/spec/fixtures/objects/amf3-float.bin +0 -0
- data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
- data/spec/fixtures/objects/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/amf3-large-max.bin +0 -0
- data/spec/fixtures/objects/amf3-large-min.bin +0 -0
- data/spec/fixtures/objects/amf3-max.bin +1 -0
- data/spec/fixtures/objects/amf3-min.bin +0 -0
- data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
- data/spec/fixtures/objects/amf3-null.bin +1 -0
- data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
- data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-string.bin +1 -0
- data/spec/fixtures/objects/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
- data/spec/fixtures/objects/amf3-true.bin +1 -0
- data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
- data/spec/fixtures/objects/amf3-vector-double.bin +0 -0
- data/spec/fixtures/objects/amf3-vector-int.bin +0 -0
- data/spec/fixtures/objects/amf3-vector-object.bin +0 -0
- data/spec/fixtures/objects/amf3-vector-uint.bin +0 -0
- data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
- data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-xml.bin +1 -0
- data/spec/serializer_spec.rb +503 -0
- data/spec/spec_helper.rb +59 -0
- metadata +128 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module RubySol
|
2
|
+
module Pure
|
3
|
+
module ReadIOHelpers #:nodoc:
|
4
|
+
def read_int8 source
|
5
|
+
source.read(1).unpack('c').first
|
6
|
+
end
|
7
|
+
|
8
|
+
def read_word8 source
|
9
|
+
source.read(1).unpack('C').first
|
10
|
+
end
|
11
|
+
|
12
|
+
def read_double source
|
13
|
+
source.read(8).unpack('G').first
|
14
|
+
end
|
15
|
+
|
16
|
+
def read_word16_network source
|
17
|
+
source.read(2).unpack('n').first
|
18
|
+
end
|
19
|
+
|
20
|
+
def read_int16_network source
|
21
|
+
str = source.read(2)
|
22
|
+
str.reverse! if byte_order_little? # swap bytes as native=little (and we want network)
|
23
|
+
str.unpack('s').first
|
24
|
+
end
|
25
|
+
|
26
|
+
def read_word32_network source
|
27
|
+
source.read(4).unpack('N').first
|
28
|
+
end
|
29
|
+
|
30
|
+
def byte_order
|
31
|
+
if [0x12345678].pack("L") == "\x12\x34\x56\x78"
|
32
|
+
:BigEndian
|
33
|
+
else
|
34
|
+
:LittleEndian
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def byte_order_little?
|
39
|
+
(byte_order == :LittleEndian) ? true : false;
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module WriteIOHelpers #:nodoc:
|
44
|
+
def pack_integer(integer)
|
45
|
+
integer = integer & 0x1fffffff
|
46
|
+
if(integer < 0x80)
|
47
|
+
[integer].pack('c')
|
48
|
+
elsif(integer < 0x4000)
|
49
|
+
[integer >> 7 & 0x7f | 0x80].pack('c')+
|
50
|
+
[integer & 0x7f].pack('c')
|
51
|
+
elsif(integer < 0x200000)
|
52
|
+
[integer >> 14 & 0x7f | 0x80].pack('c') +
|
53
|
+
[integer >> 7 & 0x7f | 0x80].pack('c') +
|
54
|
+
[integer & 0x7f].pack('c')
|
55
|
+
else
|
56
|
+
[integer >> 22 & 0x7f | 0x80].pack('c')+
|
57
|
+
[integer >> 15 & 0x7f | 0x80].pack('c')+
|
58
|
+
[integer >> 8 & 0x7f | 0x80].pack('c')+
|
59
|
+
[integer & 0xff].pack('c')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def pack_double(double)
|
64
|
+
[double].pack('G')
|
65
|
+
end
|
66
|
+
|
67
|
+
def pack_int8(val)
|
68
|
+
[val].pack('c')
|
69
|
+
end
|
70
|
+
|
71
|
+
def pack_int16_network(val)
|
72
|
+
[val].pack('n')
|
73
|
+
end
|
74
|
+
|
75
|
+
def pack_word32_network(val)
|
76
|
+
str = [val].pack('L')
|
77
|
+
str.reverse! if byte_order_little? # swap bytes as native=little (and we want network)
|
78
|
+
str
|
79
|
+
end
|
80
|
+
|
81
|
+
def byte_order
|
82
|
+
if [0x12345678].pack("L") == "\x12\x34\x56\x78"
|
83
|
+
:BigEndian
|
84
|
+
else
|
85
|
+
:LittleEndian
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def byte_order_little?
|
90
|
+
(byte_order == :LittleEndian) ? true : false;
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,490 @@
|
|
1
|
+
require 'ruby_sol/pure/io_helpers'
|
2
|
+
|
3
|
+
module RubySol
|
4
|
+
module Pure
|
5
|
+
# Pure ruby serializer for AMF0 and AMF3
|
6
|
+
class Serializer
|
7
|
+
attr_reader :stream, :version
|
8
|
+
|
9
|
+
# Pass in the class mapper instance to use when serializing. This enables
|
10
|
+
# better caching behavior in the class mapper and allows one to change
|
11
|
+
# mappings between serialization attempts.
|
12
|
+
def initialize class_mapper
|
13
|
+
@class_mapper = class_mapper
|
14
|
+
@stream = ""
|
15
|
+
@depth = 0
|
16
|
+
reset_caches()
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset_caches
|
20
|
+
@ref_cache = SerializerCache.new :object
|
21
|
+
@string_cache = SerializerCache.new :string
|
22
|
+
@object_cache = SerializerCache.new :object
|
23
|
+
@trait_cache = SerializerCache.new :string
|
24
|
+
end
|
25
|
+
|
26
|
+
# Serialize the given object using AMF0 or AMF3. Can be called from inside
|
27
|
+
# encode_amf, but make sure to pass in the proper version or it may not be
|
28
|
+
# possible to decode. Use the serializer version attribute for this.
|
29
|
+
def serialize version, obj
|
30
|
+
raise ArgumentError, "unsupported version #{version}" unless [0,3].include?(version)
|
31
|
+
@version = version
|
32
|
+
|
33
|
+
# Initialize caches
|
34
|
+
if @depth == 0
|
35
|
+
if @version == 0
|
36
|
+
@ref_cache = SerializerCache.new :object
|
37
|
+
else
|
38
|
+
@string_cache = SerializerCache.new :string
|
39
|
+
@object_cache = SerializerCache.new :object
|
40
|
+
@trait_cache = SerializerCache.new :string
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@depth += 1
|
44
|
+
|
45
|
+
# Perform serialization
|
46
|
+
if @version == 0
|
47
|
+
amf0_serialize(obj)
|
48
|
+
else
|
49
|
+
amf3_serialize(obj)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Cleanup
|
53
|
+
@depth -= 1
|
54
|
+
if @depth == 0
|
55
|
+
@ref_cache = nil
|
56
|
+
@string_cache = nil
|
57
|
+
@object_cache = nil
|
58
|
+
@trait_cache = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
return @stream
|
62
|
+
end
|
63
|
+
|
64
|
+
# Helper for writing arrays inside encode_amf. It uses the current AMF
|
65
|
+
# version to write the array.
|
66
|
+
def write_array arr
|
67
|
+
if @version == 0
|
68
|
+
amf0_write_array arr
|
69
|
+
else
|
70
|
+
amf3_write_array arr
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Helper for writing objects inside encode_amf. It uses the current AMF
|
75
|
+
# version to write the object. If you pass in a property hash, it will use
|
76
|
+
# it rather than having the class mapper determine properties. For AMF3,
|
77
|
+
# you can also specify a traits hash, which can be used to reduce serialized
|
78
|
+
# data size or serialize things as externalizable.
|
79
|
+
def write_object obj, props=nil, traits=nil
|
80
|
+
if @version == 0
|
81
|
+
amf0_write_object obj, props
|
82
|
+
else
|
83
|
+
amf3_write_object obj, props, traits
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# private
|
88
|
+
include RubySol::Pure::WriteIOHelpers
|
89
|
+
|
90
|
+
def amf0_serialize obj
|
91
|
+
if @ref_cache[obj] != nil
|
92
|
+
amf0_write_reference @ref_cache[obj]
|
93
|
+
elsif obj.respond_to?(:encode_amf)
|
94
|
+
obj.encode_amf(self)
|
95
|
+
elsif obj.is_a?(NilClass)
|
96
|
+
amf0_write_null
|
97
|
+
elsif obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
|
98
|
+
amf0_write_boolean obj
|
99
|
+
elsif obj.is_a?(Numeric)
|
100
|
+
amf0_write_number obj
|
101
|
+
elsif obj.is_a?(Symbol) || obj.is_a?(String)
|
102
|
+
amf0_write_string obj.to_s
|
103
|
+
elsif obj.is_a?(Time)
|
104
|
+
amf0_write_time obj
|
105
|
+
elsif obj.is_a?(Date)
|
106
|
+
amf0_write_date obj
|
107
|
+
elsif obj.is_a?(Array)
|
108
|
+
amf0_write_array obj
|
109
|
+
elsif obj.is_a?(Hash) ||obj.is_a?(Object)
|
110
|
+
amf0_write_object obj
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def amf0_write_null
|
115
|
+
@stream << AMF0_NULL_MARKER
|
116
|
+
end
|
117
|
+
|
118
|
+
def amf0_write_boolean bool
|
119
|
+
@stream << AMF0_BOOLEAN_MARKER
|
120
|
+
@stream << pack_int8(bool ? 1 : 0)
|
121
|
+
end
|
122
|
+
|
123
|
+
def amf0_write_number num
|
124
|
+
@stream << AMF0_NUMBER_MARKER
|
125
|
+
@stream << pack_double(num)
|
126
|
+
end
|
127
|
+
|
128
|
+
def amf0_write_string str
|
129
|
+
str = str.encode("UTF-8").force_encoding("ASCII-8BIT") if str.respond_to?(:encode)
|
130
|
+
len = str.bytesize
|
131
|
+
if len > 2**16-1
|
132
|
+
@stream << AMF0_LONG_STRING_MARKER
|
133
|
+
@stream << pack_word32_network(len)
|
134
|
+
else
|
135
|
+
@stream << AMF0_STRING_MARKER
|
136
|
+
@stream << pack_int16_network(len)
|
137
|
+
end
|
138
|
+
@stream << str
|
139
|
+
end
|
140
|
+
|
141
|
+
def amf0_write_string_wo_marker str
|
142
|
+
str = str.encode("UTF-8").force_encoding("ASCII-8BIT") if str.respond_to?(:encode)
|
143
|
+
len = str.bytesize
|
144
|
+
throw SOLError, 'too long string' if len > 2**16-1
|
145
|
+
@stream << pack_int16_network(len)
|
146
|
+
@stream << str
|
147
|
+
end
|
148
|
+
|
149
|
+
def amf0_write_time time
|
150
|
+
@stream << AMF0_DATE_MARKER
|
151
|
+
|
152
|
+
time = time.getutc # Dup and convert to UTC
|
153
|
+
milli = (time.to_f * 1000).to_i
|
154
|
+
@stream << pack_double(milli)
|
155
|
+
|
156
|
+
@stream << pack_int16_network(0) # Time zone
|
157
|
+
end
|
158
|
+
|
159
|
+
def amf0_write_date date
|
160
|
+
@stream << AMF0_DATE_MARKER
|
161
|
+
@stream << pack_double(date.strftime("%Q").to_i)
|
162
|
+
@stream << pack_int16_network(0) # Time zone
|
163
|
+
end
|
164
|
+
|
165
|
+
def amf0_write_reference index
|
166
|
+
@stream << AMF0_REFERENCE_MARKER
|
167
|
+
@stream << pack_int16_network(index)
|
168
|
+
end
|
169
|
+
|
170
|
+
def amf0_write_array array
|
171
|
+
@ref_cache.add_obj array
|
172
|
+
@stream << AMF0_STRICT_ARRAY_MARKER
|
173
|
+
@stream << pack_word32_network(array.length)
|
174
|
+
array.each do |elem|
|
175
|
+
amf0_serialize elem
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def amf0_write_object obj, props=nil
|
180
|
+
@ref_cache.add_obj obj
|
181
|
+
|
182
|
+
props = @class_mapper.props_for_serialization obj if props.nil?
|
183
|
+
|
184
|
+
# Is it a typed object?
|
185
|
+
class_name = @class_mapper.get_as_class_name obj
|
186
|
+
if class_name
|
187
|
+
class_name = class_name.encode("UTF-8").force_encoding("ASCII-8BIT") if class_name.respond_to?(:encode)
|
188
|
+
@stream << AMF0_TYPED_OBJECT_MARKER
|
189
|
+
@stream << pack_int16_network(class_name.bytesize)
|
190
|
+
@stream << class_name
|
191
|
+
else
|
192
|
+
@stream << AMF0_OBJECT_MARKER
|
193
|
+
end
|
194
|
+
|
195
|
+
# Write prop list
|
196
|
+
props.sort.each do |key, value| # Sort keys before writing
|
197
|
+
key = key.encode("UTF-8").force_encoding("ASCII-8BIT") if key.respond_to?(:encode)
|
198
|
+
@stream << pack_int16_network(key.bytesize)
|
199
|
+
@stream << key
|
200
|
+
amf0_serialize value
|
201
|
+
end
|
202
|
+
|
203
|
+
# Write end
|
204
|
+
@stream << pack_int16_network(0)
|
205
|
+
@stream << AMF0_OBJECT_END_MARKER
|
206
|
+
end
|
207
|
+
|
208
|
+
def amf3_serialize obj
|
209
|
+
if obj.respond_to?(:encode_amf)
|
210
|
+
obj.encode_amf(self)
|
211
|
+
elsif obj.is_a?(NilClass)
|
212
|
+
amf3_write_null
|
213
|
+
elsif obj.is_a?(TrueClass)
|
214
|
+
amf3_write_true
|
215
|
+
elsif obj.is_a?(FalseClass)
|
216
|
+
amf3_write_false
|
217
|
+
elsif obj.is_a?(Numeric)
|
218
|
+
amf3_write_numeric obj
|
219
|
+
elsif obj.is_a?(Symbol) || obj.is_a?(String)
|
220
|
+
amf3_write_string obj.to_s
|
221
|
+
elsif obj.is_a?(Time)
|
222
|
+
amf3_write_time obj
|
223
|
+
elsif obj.is_a?(Date)
|
224
|
+
amf3_write_date obj
|
225
|
+
elsif obj.is_a?(StringIO)
|
226
|
+
amf3_write_byte_array obj
|
227
|
+
elsif obj.is_a?(Array)
|
228
|
+
amf3_write_array obj
|
229
|
+
elsif obj.is_a?(Hash) || obj.is_a?(Object)
|
230
|
+
amf3_write_object obj
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def amf3_write_reference index
|
235
|
+
header = index << 1 # shift value left to leave a low bit of 0
|
236
|
+
@stream << pack_integer(header)
|
237
|
+
end
|
238
|
+
|
239
|
+
def amf3_write_null
|
240
|
+
@stream << AMF3_NULL_MARKER
|
241
|
+
end
|
242
|
+
|
243
|
+
def amf3_write_true
|
244
|
+
@stream << AMF3_TRUE_MARKER
|
245
|
+
end
|
246
|
+
|
247
|
+
def amf3_write_false
|
248
|
+
@stream << AMF3_FALSE_MARKER
|
249
|
+
end
|
250
|
+
|
251
|
+
def amf3_write_numeric num
|
252
|
+
if !num.integer? || num < MIN_INTEGER || num > MAX_INTEGER # Check valid range for 29 bits
|
253
|
+
@stream << AMF3_DOUBLE_MARKER
|
254
|
+
@stream << pack_double(num)
|
255
|
+
else
|
256
|
+
@stream << AMF3_INTEGER_MARKER
|
257
|
+
@stream << pack_integer(num)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def amf3_write_string str
|
262
|
+
@stream << AMF3_STRING_MARKER
|
263
|
+
amf3_write_utf8_vr str
|
264
|
+
end
|
265
|
+
|
266
|
+
def amf3_write_time time
|
267
|
+
@stream << AMF3_DATE_MARKER
|
268
|
+
if @object_cache[time] != nil
|
269
|
+
amf3_write_reference @object_cache[time]
|
270
|
+
else
|
271
|
+
# Cache time
|
272
|
+
@object_cache.add_obj time
|
273
|
+
|
274
|
+
# Build AMF string
|
275
|
+
time = time.getutc # Dup and convert to UTC
|
276
|
+
milli = (time.to_f * 1000).to_i
|
277
|
+
@stream << AMF3_NULL_MARKER
|
278
|
+
@stream << pack_double(milli)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def amf3_write_date date
|
283
|
+
@stream << AMF3_DATE_MARKER
|
284
|
+
if @object_cache[date] != nil
|
285
|
+
amf3_write_reference @object_cache[date]
|
286
|
+
else
|
287
|
+
# Cache date
|
288
|
+
@object_cache.add_obj date
|
289
|
+
|
290
|
+
# Build AMF string
|
291
|
+
@stream << AMF3_NULL_MARKER
|
292
|
+
@stream << pack_double(date.strftime("%Q").to_i)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def amf3_write_byte_array array
|
297
|
+
@stream << AMF3_BYTE_ARRAY_MARKER
|
298
|
+
if @object_cache[array] != nil
|
299
|
+
amf3_write_reference @object_cache[array]
|
300
|
+
else
|
301
|
+
@object_cache.add_obj array
|
302
|
+
str = array.string
|
303
|
+
@stream << pack_integer(str.bytesize << 1 | 1)
|
304
|
+
@stream << str
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def amf3_write_array array
|
309
|
+
# Is it an array collection?
|
310
|
+
is_ac = false
|
311
|
+
if array.respond_to?(:is_array_collection?)
|
312
|
+
is_ac = array.is_array_collection?
|
313
|
+
else
|
314
|
+
is_ac = @class_mapper.use_array_collection
|
315
|
+
end
|
316
|
+
|
317
|
+
# Write type marker
|
318
|
+
@stream << (is_ac ? AMF3_OBJECT_MARKER : AMF3_ARRAY_MARKER)
|
319
|
+
|
320
|
+
# Write reference or cache array
|
321
|
+
if @object_cache[array] != nil
|
322
|
+
amf3_write_reference @object_cache[array]
|
323
|
+
return
|
324
|
+
else
|
325
|
+
@object_cache.add_obj array
|
326
|
+
@object_cache.add_obj nil if is_ac # The array collection source array
|
327
|
+
end
|
328
|
+
|
329
|
+
# Write out traits and array marker if it's an array collection
|
330
|
+
if is_ac
|
331
|
+
class_name = "flex.messaging.io.ArrayCollection"
|
332
|
+
if @trait_cache[class_name] != nil
|
333
|
+
@stream << pack_integer(@trait_cache[class_name] << 2 | 0x01)
|
334
|
+
else
|
335
|
+
@trait_cache.add_obj class_name
|
336
|
+
@stream << "\a" # Externalizable, non-dynamic
|
337
|
+
amf3_write_utf8_vr(class_name)
|
338
|
+
end
|
339
|
+
@stream << AMF3_ARRAY_MARKER
|
340
|
+
end
|
341
|
+
|
342
|
+
# Build AMF string for array
|
343
|
+
header = array.length << 1 # make room for a low bit of 1
|
344
|
+
header = header | 1 # set the low bit to 1
|
345
|
+
@stream << pack_integer(header)
|
346
|
+
@stream << AMF3_CLOSE_DYNAMIC_ARRAY
|
347
|
+
array.each do |elem|
|
348
|
+
amf3_serialize elem
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def amf3_write_object obj, props=nil, traits=nil
|
353
|
+
@stream << AMF3_OBJECT_MARKER
|
354
|
+
|
355
|
+
# Caching...
|
356
|
+
if @object_cache[obj] != nil
|
357
|
+
amf3_write_reference @object_cache[obj]
|
358
|
+
return
|
359
|
+
end
|
360
|
+
@object_cache.add_obj obj
|
361
|
+
|
362
|
+
# Calculate traits if not given
|
363
|
+
is_default = false
|
364
|
+
if traits.nil?
|
365
|
+
traits = {
|
366
|
+
:class_name => @class_mapper.get_as_class_name(obj),
|
367
|
+
:members => [],
|
368
|
+
:externalizable => false,
|
369
|
+
:dynamic => true
|
370
|
+
}
|
371
|
+
is_default = true unless traits[:class_name]
|
372
|
+
end
|
373
|
+
class_name = is_default ? "__default__" : traits[:class_name]
|
374
|
+
|
375
|
+
# Write out traits
|
376
|
+
if (class_name && @trait_cache[class_name] != nil)
|
377
|
+
@stream << pack_integer(@trait_cache[class_name] << 2 | 0x01)
|
378
|
+
else
|
379
|
+
@trait_cache.add_obj class_name if class_name
|
380
|
+
|
381
|
+
# Write out trait header
|
382
|
+
header = 0x03 # Not object ref and not trait ref
|
383
|
+
header |= 0x02 << 2 if traits[:dynamic]
|
384
|
+
header |= 0x01 << 2 if traits[:externalizable]
|
385
|
+
header |= traits[:members].length << 4
|
386
|
+
@stream << pack_integer(header)
|
387
|
+
|
388
|
+
# Write out class name
|
389
|
+
if class_name == "__default__"
|
390
|
+
amf3_write_utf8_vr("")
|
391
|
+
else
|
392
|
+
amf3_write_utf8_vr(class_name.to_s)
|
393
|
+
end
|
394
|
+
|
395
|
+
# Write out members
|
396
|
+
traits[:members].each {|m| amf3_write_utf8_vr(m)}
|
397
|
+
end
|
398
|
+
|
399
|
+
# If externalizable, take externalized data shortcut
|
400
|
+
if traits[:externalizable]
|
401
|
+
obj.write_external(self)
|
402
|
+
return
|
403
|
+
end
|
404
|
+
|
405
|
+
# Extract properties if not given
|
406
|
+
props = @class_mapper.props_for_serialization(obj) if props.nil?
|
407
|
+
|
408
|
+
# Write out sealed properties
|
409
|
+
traits[:members].each do |m|
|
410
|
+
amf3_serialize props[m]
|
411
|
+
props.delete(m)
|
412
|
+
end
|
413
|
+
|
414
|
+
# Write out dynamic properties
|
415
|
+
if traits[:dynamic]
|
416
|
+
# Write out dynamic properties
|
417
|
+
props.sort.each do |key, val| # Sort props until Ruby 1.9 becomes common
|
418
|
+
amf3_write_utf8_vr key.to_s
|
419
|
+
amf3_serialize val
|
420
|
+
end
|
421
|
+
|
422
|
+
# Write close
|
423
|
+
@stream << AMF3_CLOSE_DYNAMIC_OBJECT
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def amf3_write_utf8_vr str, encode=true
|
428
|
+
if str.respond_to?(:encode)
|
429
|
+
if encode
|
430
|
+
str = str.encode("UTF-8")
|
431
|
+
else
|
432
|
+
str = str.dup if str.frozen?
|
433
|
+
end
|
434
|
+
str.force_encoding("ASCII-8BIT")
|
435
|
+
end
|
436
|
+
|
437
|
+
if str == ''
|
438
|
+
@stream << AMF3_EMPTY_STRING
|
439
|
+
elsif @string_cache[str] != nil
|
440
|
+
amf3_write_reference @string_cache[str]
|
441
|
+
else
|
442
|
+
# Cache string
|
443
|
+
@string_cache.add_obj str
|
444
|
+
|
445
|
+
# Build AMF string
|
446
|
+
@stream << pack_integer(str.bytesize << 1 | 1)
|
447
|
+
@stream << str
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
class SerializerCache #:nodoc:
|
453
|
+
def self.new type
|
454
|
+
if type == :string
|
455
|
+
StringCache.new
|
456
|
+
elsif type == :object
|
457
|
+
ObjectCache.new
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
class StringCache < Hash #:nodoc:
|
462
|
+
def initialize
|
463
|
+
@cache_index = 0
|
464
|
+
end
|
465
|
+
|
466
|
+
def add_obj str
|
467
|
+
self[str] = @cache_index
|
468
|
+
@cache_index += 1
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
class ObjectCache < Hash #:nodoc:
|
473
|
+
def initialize
|
474
|
+
@cache_index = 0
|
475
|
+
@obj_references = []
|
476
|
+
end
|
477
|
+
|
478
|
+
def [] obj
|
479
|
+
super(obj.object_id)
|
480
|
+
end
|
481
|
+
|
482
|
+
def add_obj obj
|
483
|
+
@obj_references << obj
|
484
|
+
self[obj.object_id] = @cache_index
|
485
|
+
@cache_index += 1
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|