amfora 0.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.
- data/.document +5 -0
- data/.gitignore +4 -0
- data/LICENSE +20 -0
- data/README.rdoc +16 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/amfora.gemspec +66 -0
- data/lib/amf.rb +70 -0
- data/lib/amf/active_record.rb +60 -0
- data/lib/amf/class_mapping.rb +137 -0
- data/lib/amf/constants.rb +47 -0
- data/lib/amf/messages.rb +145 -0
- data/lib/amf/pure.rb +14 -0
- data/lib/amf/pure/deserializer.rb +362 -0
- data/lib/amf/pure/io_helpers.rb +94 -0
- data/lib/amf/pure/remoting.rb +119 -0
- data/lib/amf/pure/serializer.rb +230 -0
- data/lib/amf/version.rb +9 -0
- data/lib/amfora.rb +2 -0
- data/lib/rack/amf.rb +12 -0
- data/lib/rack/amf/application.rb +54 -0
- data/spec/amfora_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +90 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module AMF
|
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,119 @@
|
|
1
|
+
require 'amf/pure/io_helpers'
|
2
|
+
|
3
|
+
module AMF
|
4
|
+
module Pure
|
5
|
+
# AMF request object wrapper, it is responsible for deserializing AMF requests
|
6
|
+
class Request
|
7
|
+
attr_reader :amf_version, :headers, :message
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@amf_version = 3
|
11
|
+
@headers = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def populate_from_stream(stream)
|
15
|
+
stream = StringIO.new(stream) unless StringIO === stream
|
16
|
+
|
17
|
+
# Read AMF version
|
18
|
+
@amf_version = read_word16_network(stream)
|
19
|
+
|
20
|
+
# Read in headers
|
21
|
+
header_count = read_word16_network(stream)
|
22
|
+
0.upto(header_count-1) do
|
23
|
+
name = stream.read(read_word16_network(stream))
|
24
|
+
must_understand = read_int8(stream) != 0
|
25
|
+
length = read_word32_network(stream)
|
26
|
+
data = AMF.deserialize(stream)
|
27
|
+
@headers << Header.new(name, must_understand, data)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Read in first message
|
31
|
+
message_count = read_word16_network(stream)
|
32
|
+
target_uri = stream.read(read_word16_network(stream))
|
33
|
+
response_uri = stream.read(read_word16_network(stream))
|
34
|
+
length = read_word32_network(stream)
|
35
|
+
data = AMF.deserialize(stream)
|
36
|
+
if data.is_a?(Array) && data.length == 1 && data[0].is_a?(::AMF::Messages::AbstractMessage)
|
37
|
+
data = data[0]
|
38
|
+
end
|
39
|
+
@message = Message.new(target_uri, response_uri, data)
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
include AMF::Pure::ReadIOHelpers
|
46
|
+
end
|
47
|
+
|
48
|
+
# AMF response object wrapper, it is responsible for serializing the AMF response
|
49
|
+
class Response
|
50
|
+
attr_accessor :amf_version, :headers, :message
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
@amf_version = 0
|
54
|
+
@headers = []
|
55
|
+
end
|
56
|
+
|
57
|
+
def serialize
|
58
|
+
stream = ""
|
59
|
+
|
60
|
+
# Write version
|
61
|
+
stream << pack_int16_network(@amf_version)
|
62
|
+
|
63
|
+
# Write headers
|
64
|
+
stream << pack_int16_network(@headers.length) # Header count
|
65
|
+
@headers.each do |h|
|
66
|
+
stream << pack_int16_network(h.name.length)
|
67
|
+
stream << h.name
|
68
|
+
stream << pack_int8(h.must_understand ? 1 : 0)
|
69
|
+
stream << pack_word32_network(-1)
|
70
|
+
stream << AMF.serialize(h.data, 0)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Write messages
|
74
|
+
stream << pack_int16_network(1) # we only send one message
|
75
|
+
stream << pack_int16_network(@message.target_uri.length)
|
76
|
+
stream << @message.target_uri
|
77
|
+
|
78
|
+
stream << pack_int16_network(@message.response_uri.length)
|
79
|
+
stream << @message.response_uri
|
80
|
+
|
81
|
+
stream << pack_word32_network(-1)
|
82
|
+
stream << AMF0_AMF3_MARKER if @amf_version == 3
|
83
|
+
# stream << AMF.serialize(m.data, @amf_version)
|
84
|
+
stream << @message.data
|
85
|
+
|
86
|
+
stream
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
serialize
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
include AMF::Pure::WriteIOHelpers
|
95
|
+
end
|
96
|
+
|
97
|
+
# AMF::Request or AMF::Response header
|
98
|
+
class Header
|
99
|
+
attr_accessor :name, :must_understand, :data
|
100
|
+
|
101
|
+
def initialize name, must_understand, data
|
102
|
+
@name = name
|
103
|
+
@must_understand = must_understand
|
104
|
+
@data = data
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# AMF::Request or AMF::Response message
|
109
|
+
class Message
|
110
|
+
attr_accessor :target_uri, :response_uri, :data
|
111
|
+
|
112
|
+
def initialize target_uri, response_uri, data
|
113
|
+
@target_uri = target_uri
|
114
|
+
@response_uri = response_uri
|
115
|
+
@data = data
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'amf/pure/io_helpers'
|
2
|
+
|
3
|
+
module AMF
|
4
|
+
module Pure
|
5
|
+
class AMF0Serializer
|
6
|
+
def version
|
7
|
+
0
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize(obj, &block)
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# AMF3 implementation of serializer
|
16
|
+
class AMF3Serializer
|
17
|
+
attr_accessor :stream
|
18
|
+
attr_reader :string_cache
|
19
|
+
|
20
|
+
def initialize(params = {})
|
21
|
+
@stream = ""
|
22
|
+
@string_cache = SerializerCache.new :string
|
23
|
+
@object_cache = SerializerCache.new :object
|
24
|
+
@serializable_names = params[:serializable_names]
|
25
|
+
@opts = params[:options]
|
26
|
+
end
|
27
|
+
|
28
|
+
def version
|
29
|
+
3
|
30
|
+
end
|
31
|
+
|
32
|
+
def serialize(obj, &block)
|
33
|
+
if obj.is_a?(NilClass)
|
34
|
+
write_null
|
35
|
+
elsif obj.is_a?(TrueClass)
|
36
|
+
write_true
|
37
|
+
elsif obj.is_a?(FalseClass)
|
38
|
+
write_false
|
39
|
+
elsif obj.is_a?(Float)
|
40
|
+
write_float(obj)
|
41
|
+
elsif obj.is_a?(Integer)
|
42
|
+
write_integer(obj)
|
43
|
+
elsif obj.is_a?(Symbol) || obj.is_a?(String)
|
44
|
+
write_string(obj.to_s)
|
45
|
+
elsif obj.is_a?(Time)
|
46
|
+
write_date(obj)
|
47
|
+
elsif obj.is_a?(Array)
|
48
|
+
write_array(obj, &block)
|
49
|
+
elsif obj.is_a?(Hash) || obj.is_a?(Object)
|
50
|
+
write_object(obj, &block)
|
51
|
+
end
|
52
|
+
@stream
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_reference index
|
56
|
+
header = index << 1 # shift value left to leave a low bit of 0
|
57
|
+
@stream << pack_integer(header)
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_null
|
61
|
+
@stream << AMF3_NULL_MARKER
|
62
|
+
end
|
63
|
+
|
64
|
+
def write_true
|
65
|
+
@stream << AMF3_TRUE_MARKER
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_false
|
69
|
+
@stream << AMF3_FALSE_MARKER
|
70
|
+
end
|
71
|
+
|
72
|
+
def write_integer(int)
|
73
|
+
if int < MIN_INTEGER || int > MAX_INTEGER # Check valid range for 29 bits
|
74
|
+
write_float(int.to_f)
|
75
|
+
else
|
76
|
+
@stream << AMF3_INTEGER_MARKER
|
77
|
+
@stream << pack_integer(int)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def write_float(float)
|
82
|
+
@stream << AMF3_DOUBLE_MARKER
|
83
|
+
@stream << pack_double(float)
|
84
|
+
end
|
85
|
+
|
86
|
+
def write_string(str)
|
87
|
+
@stream << AMF3_STRING_MARKER
|
88
|
+
write_utf8_vr(str)
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_date(date)
|
92
|
+
@stream << AMF3_DATE_MARKER
|
93
|
+
if @object_cache[date] != nil
|
94
|
+
write_reference(@object_cache[date])
|
95
|
+
else
|
96
|
+
# Cache date
|
97
|
+
@object_cache.add_obj(date)
|
98
|
+
|
99
|
+
# Build AMF string
|
100
|
+
date.utc unless date.utc?
|
101
|
+
seconds = (date.to_f * 1000).to_i
|
102
|
+
@stream << pack_integer(AMF3_NULL_MARKER)
|
103
|
+
@stream << pack_double(seconds)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def write_array(array)
|
108
|
+
@stream << AMF3_ARRAY_MARKER
|
109
|
+
if @object_cache[array] != nil
|
110
|
+
write_reference(@object_cache[array])
|
111
|
+
else
|
112
|
+
# Cache array
|
113
|
+
@object_cache.add_obj(array)
|
114
|
+
|
115
|
+
# Build AMF string
|
116
|
+
header = array.length << 1 # make room for a low bit of 1
|
117
|
+
header = header | 1 # set the low bit to 1
|
118
|
+
@stream << pack_integer(header)
|
119
|
+
@stream << AMF3_CLOSE_DYNAMIC_ARRAY
|
120
|
+
array.each do |elem|
|
121
|
+
if elem.respond_to?(:to_amf)
|
122
|
+
@stream << elem.to_amf(@opts)
|
123
|
+
else
|
124
|
+
serialize(elem)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
block.call(self) if block_given?
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def write_object(obj, &block)
|
133
|
+
@stream << AMF3_OBJECT_MARKER
|
134
|
+
if @object_cache[obj] != nil
|
135
|
+
write_reference(@object_cache[obj])
|
136
|
+
else
|
137
|
+
# Cache object
|
138
|
+
@object_cache.add_obj(obj)
|
139
|
+
|
140
|
+
# Always serialize things as dynamic objects
|
141
|
+
@stream << AMF3_DYNAMIC_OBJECT
|
142
|
+
|
143
|
+
if obj.is_a?(Hash)
|
144
|
+
@stream << AMF3_ANONYMOUS_OBJECT
|
145
|
+
|
146
|
+
# Stringify keys to make it easier later on and allow sorting
|
147
|
+
obj.each do |k,v|
|
148
|
+
write_utf8_vr(k.to_s)
|
149
|
+
serialize(obj[k])
|
150
|
+
end
|
151
|
+
else
|
152
|
+
# Write class name/anonymous
|
153
|
+
class_name = AMF::ClassMapper.get_as_class_name(obj)
|
154
|
+
if class_name
|
155
|
+
write_utf8_vr(class_name)
|
156
|
+
else
|
157
|
+
@stream << AMF3_ANONYMOUS_OBJECT
|
158
|
+
end
|
159
|
+
|
160
|
+
@serializable_names.sort.each do |name|
|
161
|
+
write_utf8_vr(name.to_s.camelize(:lower))
|
162
|
+
serialize(obj.send(name))
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
block.call(self) if block_given?
|
167
|
+
|
168
|
+
# Write close
|
169
|
+
@stream << AMF3_CLOSE_DYNAMIC_OBJECT
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def write_utf8_vr(str)
|
174
|
+
if str == ''
|
175
|
+
@stream << AMF3_EMPTY_STRING
|
176
|
+
elsif @string_cache[str] != nil
|
177
|
+
write_reference(@string_cache[str])
|
178
|
+
else
|
179
|
+
# Cache string
|
180
|
+
@string_cache.add_obj(str)
|
181
|
+
|
182
|
+
# Build AMF string
|
183
|
+
header = str.length << 1 # make room for a low bit of 1
|
184
|
+
header = header | 1 # set the low bit to 1
|
185
|
+
@stream << pack_integer(header)
|
186
|
+
@stream << str
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
include AMF::Pure::WriteIOHelpers
|
192
|
+
end
|
193
|
+
|
194
|
+
class SerializerCache #:nodoc:all:
|
195
|
+
def self.new(type)
|
196
|
+
if type == :string
|
197
|
+
StringCache.new
|
198
|
+
elsif type == :object
|
199
|
+
ObjectCache.new
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class StringCache < Hash
|
204
|
+
def initialize
|
205
|
+
@cache_index = 0
|
206
|
+
end
|
207
|
+
|
208
|
+
def add_obj(str)
|
209
|
+
self[str] = @cache_index
|
210
|
+
@cache_index += 1
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
class ObjectCache < Hash
|
215
|
+
def initialize
|
216
|
+
@cache_index = 0
|
217
|
+
end
|
218
|
+
|
219
|
+
def [](obj)
|
220
|
+
super(obj.object_id)
|
221
|
+
end
|
222
|
+
|
223
|
+
def add_obj(obj)
|
224
|
+
self[obj.object_id] = @cache_index
|
225
|
+
@cache_index += 1
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
data/lib/amf/version.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module AMF
|
2
|
+
# AMF version
|
3
|
+
VERSION = '0.0.1'
|
4
|
+
VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
|
5
|
+
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
|
6
|
+
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
|
7
|
+
VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
|
8
|
+
VARIANT_BINARY = false
|
9
|
+
end
|