scotttam-RocketAMF 0.2.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.
- data/README.rdoc +45 -0
- data/Rakefile +54 -0
- data/lib/rocketamf.rb +128 -0
- data/lib/rocketamf/class_mapping.rb +231 -0
- data/lib/rocketamf/constants.rb +46 -0
- data/lib/rocketamf/pure.rb +28 -0
- data/lib/rocketamf/pure/deserializer.rb +419 -0
- data/lib/rocketamf/pure/io_helpers.rb +94 -0
- data/lib/rocketamf/pure/remoting.rb +134 -0
- data/lib/rocketamf/pure/serializer.rb +433 -0
- data/lib/rocketamf/remoting.rb +144 -0
- data/lib/rocketamf/values/array_collection.rb +13 -0
- data/lib/rocketamf/values/messages.rb +133 -0
- data/lib/rocketamf/values/typed_hash.rb +13 -0
- data/spec/amf/class_mapping_spec.rb +150 -0
- data/spec/amf/deserializer_spec.rb +367 -0
- data/spec/amf/remoting_spec.rb +132 -0
- data/spec/amf/serializer_spec.rb +384 -0
- data/spec/amf/values/array_collection_spec.rb +19 -0
- data/spec/amf/values/messages_spec.rb +31 -0
- data/spec/fixtures/objects/amf0-boolean.bin +1 -0
- data/spec/fixtures/objects/amf0-complexEncodedStringArray.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-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-xmlDoc.bin +0 -0
- data/spec/fixtures/objects/amf3-0.bin +0 -0
- data/spec/fixtures/objects/amf3-arrayCollection.bin +2 -0
- data/spec/fixtures/objects/amf3-arrayRef.bin +1 -0
- data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/amf3-byteArray.bin +0 -0
- data/spec/fixtures/objects/amf3-byteArrayRef.bin +1 -0
- data/spec/fixtures/objects/amf3-complexEncodedStringArray.bin +1 -0
- data/spec/fixtures/objects/amf3-date.bin +0 -0
- data/spec/fixtures/objects/amf3-datesRef.bin +0 -0
- data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-dynObject.bin +2 -0
- data/spec/fixtures/objects/amf3-emptyArray.bin +1 -0
- data/spec/fixtures/objects/amf3-emptyArrayRef.bin +1 -0
- data/spec/fixtures/objects/amf3-emptyDictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-emptyStringRef.bin +1 -0
- data/spec/fixtures/objects/amf3-encodedStringRef.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-graphMember.bin +0 -0
- data/spec/fixtures/objects/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/amf3-largeMax.bin +0 -0
- data/spec/fixtures/objects/amf3-largeMin.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-mixedArray.bin +11 -0
- data/spec/fixtures/objects/amf3-null.bin +1 -0
- data/spec/fixtures/objects/amf3-objRef.bin +0 -0
- data/spec/fixtures/objects/amf3-primArray.bin +1 -0
- data/spec/fixtures/objects/amf3-string.bin +1 -0
- data/spec/fixtures/objects/amf3-stringRef.bin +0 -0
- data/spec/fixtures/objects/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/amf3-traitRef.bin +3 -0
- data/spec/fixtures/objects/amf3-true.bin +1 -0
- data/spec/fixtures/objects/amf3-typedObject.bin +2 -0
- data/spec/fixtures/objects/amf3-xml.bin +1 -0
- data/spec/fixtures/objects/amf3-xmlDoc.bin +1 -0
- data/spec/fixtures/objects/amf3-xmlRef.bin +1 -0
- data/spec/fixtures/request/acknowledge-response.bin +0 -0
- data/spec/fixtures/request/amf0-error-response.bin +0 -0
- data/spec/fixtures/request/commandMessage.bin +0 -0
- data/spec/fixtures/request/remotingMessage.bin +0 -0
- data/spec/fixtures/request/simple-response.bin +0 -0
- data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +31 -0
- metadata +153 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module RocketAMF
|
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,134 @@
|
|
1
|
+
require 'rocketamf/pure/io_helpers'
|
2
|
+
|
3
|
+
module RocketAMF
|
4
|
+
module Pure
|
5
|
+
# Included into RocketAMF::Envelope, this module replaces the
|
6
|
+
# populate_from_stream and serialize methods with actual working versions
|
7
|
+
module Envelope
|
8
|
+
# Included into RocketAMF::Envelope, this method handles deserializing an
|
9
|
+
# AMF request/response into the envelope
|
10
|
+
def populate_from_stream stream
|
11
|
+
stream = StringIO.new(stream) unless StringIO === stream
|
12
|
+
|
13
|
+
# Initialize
|
14
|
+
@amf_version = 0
|
15
|
+
@headers = []
|
16
|
+
@messages = []
|
17
|
+
|
18
|
+
# Read AMF version
|
19
|
+
@amf_version = read_word16_network stream
|
20
|
+
|
21
|
+
# Read in headers
|
22
|
+
header_count = read_word16_network stream
|
23
|
+
0.upto(header_count-1) do
|
24
|
+
name = stream.read(read_word16_network(stream))
|
25
|
+
name.force_encoding("UTF-8") if name.respond_to?(:force_encoding)
|
26
|
+
must_understand = read_int8(stream) != 0
|
27
|
+
length = read_word32_network stream
|
28
|
+
data = RocketAMF.deserialize stream
|
29
|
+
@headers << RocketAMF::Header.new(name, must_understand, data)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Read in messages
|
33
|
+
message_count = read_word16_network stream
|
34
|
+
0.upto(message_count-1) do
|
35
|
+
target_uri = stream.read(read_word16_network(stream))
|
36
|
+
target_uri.force_encoding("UTF-8") if target_uri.respond_to?(:force_encoding)
|
37
|
+
response_uri = stream.read(read_word16_network(stream))
|
38
|
+
response_uri.force_encoding("UTF-8") if response_uri.respond_to?(:force_encoding)
|
39
|
+
length = read_word32_network stream
|
40
|
+
data = RocketAMF.deserialize stream
|
41
|
+
if data.is_a?(Array) && data.length == 1 && data[0].is_a?(::RocketAMF::Values::AbstractMessage)
|
42
|
+
data = data[0]
|
43
|
+
end
|
44
|
+
@messages << RocketAMF::Message.new(target_uri, response_uri, data)
|
45
|
+
end
|
46
|
+
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Included into RocketAMF::Envelope, this method handles serializing an
|
51
|
+
# AMF request/response into the envelope
|
52
|
+
def serialize
|
53
|
+
stream = ""
|
54
|
+
|
55
|
+
# Write version
|
56
|
+
stream << pack_int16_network(@amf_version)
|
57
|
+
|
58
|
+
# Write headers
|
59
|
+
stream << pack_int16_network(@headers.length) # Header count
|
60
|
+
@headers.each do |h|
|
61
|
+
name_str = h.name
|
62
|
+
name_str.encode!("UTF-8").force_encoding("ASCII-8BIT") if name_str.respond_to?(:encode)
|
63
|
+
stream << pack_int16_network(name_str.bytesize)
|
64
|
+
stream << name_str
|
65
|
+
stream << pack_int8(h.must_understand ? 1 : 0)
|
66
|
+
stream << pack_word32_network(-1)
|
67
|
+
stream << RocketAMF.serialize(h.data, 0)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Write messages
|
71
|
+
stream << pack_int16_network(@messages.length) # Message count
|
72
|
+
@messages.each do |m|
|
73
|
+
uri_str = m.target_uri
|
74
|
+
uri_str.encode!("UTF-8").force_encoding("ASCII-8BIT") if uri_str.respond_to?(:encode)
|
75
|
+
stream << pack_int16_network(uri_str.bytesize)
|
76
|
+
stream << uri_str
|
77
|
+
|
78
|
+
uri_str = m.response_uri
|
79
|
+
uri_str.encode!("UTF-8").force_encoding("ASCII-8BIT") if uri_str.respond_to?(:encode)
|
80
|
+
stream << pack_int16_network(uri_str.bytesize)
|
81
|
+
stream << uri_str
|
82
|
+
|
83
|
+
stream << pack_word32_network(-1)
|
84
|
+
stream << AMF0_AMF3_MARKER if @amf_version == 3
|
85
|
+
stream << RocketAMF.serialize(m.data, @amf_version)
|
86
|
+
end
|
87
|
+
|
88
|
+
stream
|
89
|
+
end
|
90
|
+
|
91
|
+
def deserialize(stream)
|
92
|
+
return nil unless stream
|
93
|
+
stream = StringIO.new(stream) unless StringIO === stream
|
94
|
+
|
95
|
+
amf_hash = {}
|
96
|
+
amf_hash[:amf_version] = read_int16_network(stream)
|
97
|
+
|
98
|
+
headers = []
|
99
|
+
header_count = read_int16_network(stream)
|
100
|
+
header_count.to_i.times do
|
101
|
+
header = {}
|
102
|
+
header_name_length = read_int16_network(stream)
|
103
|
+
header[:name] = stream.read(header_name_length)
|
104
|
+
header[:must_understand] = read_int8(stream)
|
105
|
+
buffer = read_word32_network(stream)
|
106
|
+
header[:content] = RocketAMF.deserialize(stream, @amf_version)
|
107
|
+
headers << header
|
108
|
+
end
|
109
|
+
amf_hash[:headers] = headers
|
110
|
+
|
111
|
+
bodies = []
|
112
|
+
body_count = read_int16_network(stream)
|
113
|
+
body_count.to_i.times do
|
114
|
+
body = {}
|
115
|
+
target_uri_length = read_int16_network(stream)
|
116
|
+
body[:target_uri] = stream.read(target_uri_length)
|
117
|
+
response_uri_length = read_int16_network(stream)
|
118
|
+
body[:response_uri] = stream.read(response_uri_length)
|
119
|
+
buffer = read_word32_network(stream)
|
120
|
+
amf_version_3 = read_int16_network(stream) if @amf_version == 3
|
121
|
+
body[:content] = RocketAMF.deserialize(stream, @amf_version)
|
122
|
+
bodies << body
|
123
|
+
end
|
124
|
+
amf_hash[:bodies] = bodies
|
125
|
+
|
126
|
+
amf_hash
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
include RocketAMF::Pure::ReadIOHelpers
|
131
|
+
include RocketAMF::Pure::WriteIOHelpers
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,433 @@
|
|
1
|
+
require 'rocketamf/pure/io_helpers'
|
2
|
+
|
3
|
+
module RocketAMF
|
4
|
+
module Pure
|
5
|
+
# AMF0 implementation of serializer
|
6
|
+
class Serializer
|
7
|
+
attr_reader :ref_cache, :stream
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@ref_cache = SerializerCache.new :object
|
11
|
+
@stream = ""
|
12
|
+
end
|
13
|
+
|
14
|
+
def version
|
15
|
+
0
|
16
|
+
end
|
17
|
+
|
18
|
+
def serialize obj
|
19
|
+
if @ref_cache[obj] != nil
|
20
|
+
write_reference @ref_cache[obj]
|
21
|
+
elsif obj.respond_to?(:encode_amf)
|
22
|
+
obj.encode_amf(self)
|
23
|
+
elsif obj.is_a?(NilClass)
|
24
|
+
write_null
|
25
|
+
elsif obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
|
26
|
+
write_boolean obj
|
27
|
+
elsif obj.is_a?(Float) || obj.is_a?(Integer)
|
28
|
+
write_number obj
|
29
|
+
elsif obj.is_a?(Symbol) || obj.is_a?(String)
|
30
|
+
write_string obj.to_s
|
31
|
+
elsif obj.is_a?(Time)
|
32
|
+
write_date obj
|
33
|
+
elsif obj.is_a?(Array)
|
34
|
+
write_array obj
|
35
|
+
elsif obj.is_a?(Hash)
|
36
|
+
write_hash obj
|
37
|
+
elsif obj.is_a?(Object)
|
38
|
+
write_object obj
|
39
|
+
end
|
40
|
+
@stream
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_null
|
44
|
+
@stream << AMF0_NULL_MARKER
|
45
|
+
end
|
46
|
+
|
47
|
+
def write_boolean bool
|
48
|
+
@stream << AMF0_BOOLEAN_MARKER
|
49
|
+
@stream << pack_int8(bool ? 1 : 0)
|
50
|
+
end
|
51
|
+
|
52
|
+
def write_number num
|
53
|
+
@stream << AMF0_NUMBER_MARKER
|
54
|
+
@stream << pack_double(num)
|
55
|
+
end
|
56
|
+
|
57
|
+
def write_string str
|
58
|
+
str = str.encode("UTF-8").force_encoding("ASCII-8BIT") if str.respond_to?(:encode)
|
59
|
+
len = str.bytesize
|
60
|
+
if len > 2**16-1
|
61
|
+
@stream << AMF0_LONG_STRING_MARKER
|
62
|
+
@stream << pack_word32_network(len)
|
63
|
+
else
|
64
|
+
@stream << AMF0_STRING_MARKER
|
65
|
+
@stream << pack_int16_network(len)
|
66
|
+
end
|
67
|
+
@stream << str
|
68
|
+
end
|
69
|
+
|
70
|
+
def write_date date
|
71
|
+
@stream << AMF0_DATE_MARKER
|
72
|
+
|
73
|
+
date = date.getutc # Dup and convert to UTC
|
74
|
+
milli = (date.to_f * 1000).to_i
|
75
|
+
@stream << pack_double(milli)
|
76
|
+
|
77
|
+
@stream << pack_int16_network(0) # Time zone
|
78
|
+
end
|
79
|
+
|
80
|
+
def write_reference index
|
81
|
+
@stream << AMF0_REFERENCE_MARKER
|
82
|
+
@stream << pack_int16_network(index)
|
83
|
+
end
|
84
|
+
|
85
|
+
def write_array array
|
86
|
+
@ref_cache.add_obj array
|
87
|
+
@stream << AMF0_STRICT_ARRAY_MARKER
|
88
|
+
@stream << pack_word32_network(array.length)
|
89
|
+
array.each do |elem|
|
90
|
+
serialize elem
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def write_hash hash
|
95
|
+
@ref_cache.add_obj hash
|
96
|
+
@stream << AMF0_HASH_MARKER
|
97
|
+
@stream << pack_word32_network(hash.length)
|
98
|
+
write_prop_list RocketAMF::ClassMapper.props_for_serialization(hash)
|
99
|
+
end
|
100
|
+
|
101
|
+
def write_object obj
|
102
|
+
@ref_cache.add_obj obj
|
103
|
+
props = RocketAMF::ClassMapper.props_for_serialization obj
|
104
|
+
write_custom_object obj, props, false
|
105
|
+
end
|
106
|
+
|
107
|
+
# Used to write out custom objects when writing a custom <tt>encode_amf</tt>
|
108
|
+
# method. The class name is taken from the given obj, and the props array
|
109
|
+
# is serialized as the contents of the object.
|
110
|
+
def write_custom_object obj, props, do_cache=true
|
111
|
+
@ref_cache.add_obj obj if do_cache
|
112
|
+
|
113
|
+
# Is it a typed object?
|
114
|
+
class_name = RocketAMF::ClassMapper.get_as_class_name obj
|
115
|
+
if class_name
|
116
|
+
class_name = class_name.encode("UTF-8").force_encoding("ASCII-8BIT") if class_name.respond_to?(:encode)
|
117
|
+
@stream << AMF0_TYPED_OBJECT_MARKER
|
118
|
+
@stream << pack_int16_network(class_name.bytesize)
|
119
|
+
@stream << class_name
|
120
|
+
else
|
121
|
+
@stream << AMF0_OBJECT_MARKER
|
122
|
+
end
|
123
|
+
|
124
|
+
write_prop_list props
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
include RocketAMF::Pure::WriteIOHelpers
|
129
|
+
def write_prop_list obj
|
130
|
+
# Write prop list
|
131
|
+
props = RocketAMF::ClassMapper.props_for_serialization obj
|
132
|
+
props.sort.each do |key, value| # Sort keys before writing
|
133
|
+
key = key.encode("UTF-8").force_encoding("ASCII-8BIT") if key.respond_to?(:encode)
|
134
|
+
@stream << pack_int16_network(key.bytesize)
|
135
|
+
@stream << key
|
136
|
+
serialize value
|
137
|
+
end
|
138
|
+
|
139
|
+
# Write end
|
140
|
+
@stream << pack_int16_network(0)
|
141
|
+
@stream << AMF0_OBJECT_END_MARKER
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# AMF3 implementation of serializer
|
146
|
+
class AMF3Serializer
|
147
|
+
attr_reader :string_cache, :object_cache, :trait_cache, :stream
|
148
|
+
|
149
|
+
def initialize
|
150
|
+
@string_cache = SerializerCache.new :string
|
151
|
+
@object_cache = SerializerCache.new :object
|
152
|
+
@trait_cache = SerializerCache.new :trait
|
153
|
+
@stream = ""
|
154
|
+
end
|
155
|
+
|
156
|
+
def version
|
157
|
+
3
|
158
|
+
end
|
159
|
+
|
160
|
+
def serialize obj
|
161
|
+
if obj.respond_to?(:encode_amf)
|
162
|
+
obj.encode_amf(self)
|
163
|
+
elsif obj.is_a?(NilClass)
|
164
|
+
write_null
|
165
|
+
elsif obj.is_a?(TrueClass)
|
166
|
+
write_true
|
167
|
+
elsif obj.is_a?(FalseClass)
|
168
|
+
write_false
|
169
|
+
elsif obj.is_a?(Float)
|
170
|
+
write_float obj
|
171
|
+
elsif obj.is_a?(Integer)
|
172
|
+
write_integer obj
|
173
|
+
elsif obj.is_a?(Symbol) || obj.is_a?(String)
|
174
|
+
write_string obj.to_s
|
175
|
+
elsif obj.is_a?(Time)
|
176
|
+
write_date obj
|
177
|
+
elsif obj.is_a?(StringIO)
|
178
|
+
write_byte_array obj
|
179
|
+
elsif obj.is_a?(RocketAMF::Values::ArrayCollection)
|
180
|
+
write_array_collection obj
|
181
|
+
elsif obj.is_a?(Array)
|
182
|
+
write_array obj
|
183
|
+
elsif obj.is_a?(Hash) || obj.is_a?(Object)
|
184
|
+
write_object obj
|
185
|
+
end
|
186
|
+
@stream
|
187
|
+
end
|
188
|
+
|
189
|
+
def write_reference index
|
190
|
+
header = index << 1 # shift value left to leave a low bit of 0
|
191
|
+
@stream << pack_integer(header)
|
192
|
+
end
|
193
|
+
|
194
|
+
def write_null
|
195
|
+
@stream << AMF3_NULL_MARKER
|
196
|
+
end
|
197
|
+
|
198
|
+
def write_true
|
199
|
+
@stream << AMF3_TRUE_MARKER
|
200
|
+
end
|
201
|
+
|
202
|
+
def write_false
|
203
|
+
@stream << AMF3_FALSE_MARKER
|
204
|
+
end
|
205
|
+
|
206
|
+
def write_integer int
|
207
|
+
if int < MIN_INTEGER || int > MAX_INTEGER # Check valid range for 29 bits
|
208
|
+
write_float int.to_f
|
209
|
+
else
|
210
|
+
@stream << AMF3_INTEGER_MARKER
|
211
|
+
@stream << pack_integer(int)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def write_float float
|
216
|
+
@stream << AMF3_DOUBLE_MARKER
|
217
|
+
@stream << pack_double(float)
|
218
|
+
end
|
219
|
+
|
220
|
+
def write_string str
|
221
|
+
@stream << AMF3_STRING_MARKER
|
222
|
+
write_utf8_vr str
|
223
|
+
end
|
224
|
+
|
225
|
+
def write_date date
|
226
|
+
@stream << AMF3_DATE_MARKER
|
227
|
+
if @object_cache[date] != nil
|
228
|
+
write_reference @object_cache[date]
|
229
|
+
else
|
230
|
+
# Cache date
|
231
|
+
@object_cache.add_obj date
|
232
|
+
|
233
|
+
# Build AMF string
|
234
|
+
date = date.getutc # Dup and convert to UTC
|
235
|
+
milli = (date.to_f * 1000).to_i
|
236
|
+
@stream << AMF3_NULL_MARKER
|
237
|
+
@stream << pack_double(milli)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def write_byte_array array
|
242
|
+
@stream << AMF3_BYTE_ARRAY_MARKER
|
243
|
+
if @object_cache[array] != nil
|
244
|
+
write_reference @object_cache[array]
|
245
|
+
else
|
246
|
+
@object_cache.add_obj array
|
247
|
+
write_utf8_vr array.string
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def write_array_collection array
|
252
|
+
write_custom_object array, nil, {:class_name => RocketAMF::ClassMapper.get_as_class_name(array), :members => [], :externalizable => true, :dynamic => false}
|
253
|
+
end
|
254
|
+
|
255
|
+
def write_array array
|
256
|
+
@stream << AMF3_ARRAY_MARKER
|
257
|
+
if @object_cache[array] != nil
|
258
|
+
write_reference @object_cache[array]
|
259
|
+
else
|
260
|
+
# Cache array
|
261
|
+
@object_cache.add_obj array
|
262
|
+
|
263
|
+
# Build AMF string
|
264
|
+
header = array.length << 1 # make room for a low bit of 1
|
265
|
+
header = header | 1 # set the low bit to 1
|
266
|
+
@stream << pack_integer(header)
|
267
|
+
@stream << AMF3_CLOSE_DYNAMIC_ARRAY
|
268
|
+
array.each do |elem|
|
269
|
+
serialize elem
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def write_object obj, traits=nil
|
275
|
+
@stream << AMF3_OBJECT_MARKER
|
276
|
+
if @object_cache[obj] != nil
|
277
|
+
write_reference @object_cache[obj]
|
278
|
+
else
|
279
|
+
# Cache object
|
280
|
+
@object_cache.add_obj obj
|
281
|
+
|
282
|
+
# Calculate traits if not given
|
283
|
+
if traits.nil?
|
284
|
+
traits = {
|
285
|
+
:class_name => RocketAMF::ClassMapper.get_as_class_name(obj),
|
286
|
+
:members => [],
|
287
|
+
:externalizable => false,
|
288
|
+
:dynamic => true
|
289
|
+
}
|
290
|
+
end
|
291
|
+
|
292
|
+
# Write object
|
293
|
+
props = RocketAMF::ClassMapper.props_for_serialization obj
|
294
|
+
write_custom_object obj, props, traits, false
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Used to write out custom objects when writing a custom <tt>encode_amf</tt>
|
299
|
+
# method. The class name is taken from the given obj, and the props array
|
300
|
+
# is serialized as the contents of the object.
|
301
|
+
def write_custom_object obj, props, traits, do_cache=true
|
302
|
+
if do_cache
|
303
|
+
@stream << AMF3_OBJECT_MARKER
|
304
|
+
if @object_cache[obj] != nil
|
305
|
+
write_reference @object_cache[obj]
|
306
|
+
return
|
307
|
+
else
|
308
|
+
# Cache object
|
309
|
+
@object_cache.add_obj obj
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Write out traits
|
314
|
+
if traits[:class_name] && @trait_cache[traits] != nil
|
315
|
+
@stream << pack_integer(@trait_cache[traits] << 2 | 0x01)
|
316
|
+
else
|
317
|
+
@trait_cache.add_obj traits if traits[:class_name]
|
318
|
+
|
319
|
+
# Write out trait header
|
320
|
+
header = 0x03 # Not object ref and not trait ref
|
321
|
+
header |= 0x02 << 2 if traits[:dynamic]
|
322
|
+
header |= 0x01 << 2 if traits[:externalizable]
|
323
|
+
header |= traits[:members].length << 4
|
324
|
+
@stream << pack_integer(header)
|
325
|
+
|
326
|
+
# Write out class name
|
327
|
+
write_utf8_vr(traits[:class_name].to_s)
|
328
|
+
|
329
|
+
# Write out members
|
330
|
+
traits[:members].each {|m| write_utf8_vr(m)}
|
331
|
+
end
|
332
|
+
|
333
|
+
# If externalizable, take externalized data shortcut
|
334
|
+
if traits[:externalizable]
|
335
|
+
serialize obj.externalized_data
|
336
|
+
return
|
337
|
+
end
|
338
|
+
|
339
|
+
# Write out sealed properties
|
340
|
+
traits[:members].each do |m|
|
341
|
+
serialize props[m]
|
342
|
+
props.delete(m)
|
343
|
+
end
|
344
|
+
|
345
|
+
if traits[:dynamic]
|
346
|
+
# Write out dynamic properties
|
347
|
+
props.sort.each do |key, val| # Sort props until Ruby 1.9 becomes common
|
348
|
+
write_utf8_vr key.to_s
|
349
|
+
serialize val
|
350
|
+
end
|
351
|
+
|
352
|
+
# Write close
|
353
|
+
@stream << AMF3_CLOSE_DYNAMIC_OBJECT
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
private
|
358
|
+
include RocketAMF::Pure::WriteIOHelpers
|
359
|
+
|
360
|
+
def write_utf8_vr str
|
361
|
+
str = str.encode("UTF-8").force_encoding("ASCII-8BIT") if str.respond_to?(:encode)
|
362
|
+
|
363
|
+
if str == ''
|
364
|
+
@stream << AMF3_EMPTY_STRING
|
365
|
+
elsif @string_cache[str] != nil
|
366
|
+
write_reference @string_cache[str]
|
367
|
+
else
|
368
|
+
# Cache string
|
369
|
+
@string_cache.add_obj str
|
370
|
+
|
371
|
+
# Build AMF string
|
372
|
+
header = str.bytesize << 1 # make room for a low bit of 1
|
373
|
+
header = header | 1 # set the low bit to 1
|
374
|
+
@stream << pack_integer(header)
|
375
|
+
@stream << str
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
class SerializerCache #:nodoc:
|
381
|
+
def self.new type
|
382
|
+
if type == :string
|
383
|
+
StringCache.new
|
384
|
+
elsif type == :object
|
385
|
+
ObjectCache.new
|
386
|
+
elsif type == :trait
|
387
|
+
TraitCache.new
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
class StringCache < Hash #:nodoc:
|
392
|
+
def initialize
|
393
|
+
@cache_index = 0
|
394
|
+
end
|
395
|
+
|
396
|
+
def add_obj str
|
397
|
+
self[str] = @cache_index
|
398
|
+
@cache_index += 1
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
class ObjectCache < Hash #:nodoc:
|
403
|
+
def initialize
|
404
|
+
@cache_index = 0
|
405
|
+
end
|
406
|
+
|
407
|
+
def [] obj
|
408
|
+
super(obj.object_id)
|
409
|
+
end
|
410
|
+
|
411
|
+
def add_obj obj
|
412
|
+
self[obj.object_id] = @cache_index
|
413
|
+
@cache_index += 1
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
class TraitCache < Hash #:nodoc:
|
418
|
+
def initialize
|
419
|
+
@cache_index = 0
|
420
|
+
end
|
421
|
+
|
422
|
+
def [] obj
|
423
|
+
super(obj[:class_name])
|
424
|
+
end
|
425
|
+
|
426
|
+
def add_obj obj
|
427
|
+
self[obj[:class_name]] = @cache_index
|
428
|
+
@cache_index += 1
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|