Skip to content

Commit

Permalink
Initial stab at AMF parsing. Handles strings, numbers, booleans, and …
Browse files Browse the repository at this point in the history
…objects (hashes).
  • Loading branch information
threedaymonk committed Jun 28, 2008
1 parent a310580 commit b8d15de
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 0 deletions.
79 changes: 79 additions & 0 deletions lib/net/rtmp/amf.rb
@@ -0,0 +1,79 @@
require 'net/rtmp/packet'
require 'stringio'

module Net
class RTMP
class AMF

EndOfPacket = Class.new

DT_NUMBER = 0x00
DT_BOOLEAN = 0x01
DT_STRING = 0x02
DT_OBJECT = 0x03
DT_MOVIECLIP = 0x04
DT_NULL_VALUE = 0x05
DT_UNDEFINED = 0x06
DT_REFERENCE = 0x07
DT_ECMA_ARRAY = 0x08
DT_OBJECT_END = 0x09
DT_STRICT_ARRAY = 0x0a
DT_DATE = 0x0b
DT_LONG_STRING = 0x0c
DT_UNSUPPORTED = 0x0d
DT_RECORD_SET = 0x0e
DT_XML_OBJECT = 0x0f
DT_TYPED_OBJECT = 0x10

def initialize
@elements = []
end

def parse(data)
@elements = recursive_parse(StringIO.new(data))
end

def to_a
@elements
end

private

def recursive_parse(io)
elements = []
until (e = next_element(io)) == EndOfPacket
elements << e
end
elements
end

def next_element(io)
data_type = io.read(1).unpack('C')[0]
case data_type
when DT_NUMBER
io.read(8).unpack('G')[0]
when DT_BOOLEAN
io.read(1).unpack('C')[0] != 0
when DT_OBJECT
hash = {}
until (key = read_length_prefixed_data(io)) == ''
hash[key] = next_element(io)
end
hash
when DT_STRING, DT_LONG_STRING
read_length_prefixed_data(io)
when DT_OBJECT_END
EndOfPacket
else
read_length_prefixed_data(io)
end
end

def read_length_prefixed_data(io)
length = io.read(2).unpack('n')[0]
io.read(length)
end

end
end
end
80 changes: 80 additions & 0 deletions test/test_amf.rb
@@ -0,0 +1,80 @@
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'net/rtmp/amf'
require 'test/unit'
require 'mocha'

class AMFTest < Test::Unit::TestCase

SAMPLE = %w[
02 00 07 63 6f 6e 6e 65 63 74 00 3f f0 00 00 00
00 00 00 03 00 03 61 70 70 02 00 c5 6f 6e 64 65
6d 61 6e 64 3f 5f 66 63 73 5f 76 68 6f 73 74 3d
63 70 34 38 31 38 34 2e 65 64 67 65 66 63 73 2e
6e 65 74 26 61 75 74 68 3d 64 61 45 63 49 61 4b
61 51 64 66 61 69 63 5a 63 42 61 5f 61 4c 61 39
64 59 62 68 64 43 61 43 63 33 64 39 2d 62 69 7a
77 51 42 2d 63 43 70 2d 46 6e 72 44 43 71 42 6e
4e 44 6f 47 75 77 46 26 61 69 66 70 3d 76 30 30
31 26 73 6c 69 73 74 3d 73 65 63 75 72 65 2f 36
6d 75 73 69 63 2f 41 4d 49 5f 65 36 64 30 31 62
66 36 33 39 66 65 33 37 62 65 33 61 34 32 65 34
32 33 66 39 66 33 38 34 32 35 5f 62 30 30 63 37
33 64 32 5f 36 6d 5f 6c 61 6d 61 63 71 5f 74 68
75 00 08 66 6c 61 73 68 56 65 72 02 00 0d 4c 4e
58 20 39 2c 30 2c 31 32 34 2c 30 00 06 73 77 66
55 72 6c 02 00 31 68 74 74 70 3a 2f 2f 77 77 77
2e 62 62 63 2e 63 6f 2e 75 6b 2f 65 6d 70 2f 70
6c 61 79 65 72 2e 73 77 66 3f 72 65 76 69 73 69
6f 6e 3d 33 37 30 34 00 05 74 63 55 72 6c 02 00
df 72 74 6d 70 3a 2f 2f 38 34 2e 35 33 2e 31 37
37 2e 31 34 30 3a 31 39 33 35 2f 6f 6e 64 65 6d
61 6e 64 3f 5f 66 63 73 5f 76 68 6f 73 74 3d 63
70 34 38 31 38 34 2e 65 64 67 65 66 63 73 2e 6e
65 74 26 61 75 74 68 3d 64 61 45 63 49 61 4b 61
51 64 66 61 69 63 5a 63 42 61 5f 61 4c 61 39 64
59 62 68 64 43 61 43 63 33 64 39 2d 62 69 7a 77
51 42 2d 63 43 70 2d 46 6e 72 44 43 71 42 6e 4e
44 6f 47 75 77 46 26 61 69 66 70 3d 76 30 30 31
26 73 6c 69 73 74 3d 73 65 63 75 72 65 2f 36 6d
75 73 69 63 2f 41 4d 49 5f 65 36 64 30 31 62 66
36 33 39 66 65 33 37 62 65 33 61 34 32 65 34 32
33 66 39 66 33 38 34 32 35 5f 62 30 30 63 37 33
64 32 5f 36 6d 5f 6c 61 6d 61 63 71 5f 74 68 75
00 04 66 70 61 64 01 00 00 0c 63 61 70 61 62 69
6c 69 74 69 65 73 00 40 2e 00 00 00 00 00 00 00
0b 61 75 64 69 6f 43 6f 64 65 63 73 00 40 99 9c
00 00 00 00 00 00 0b 76 69 64 65 6f 43 6f 64 65
63 73 00 40 6f 80 00 00 00 00 00 00 0d 76 69 64
65 6f 46 75 6e 63 74 69 6f 6e 00 3f f0 00 00 00
00 00 00 00 07 70 61 67 65 55 72 6c 02 00 31 68
74 74 70 3a 2f 2f 77 77 77 2e 62 62 63 2e 63 6f
2e 75 6b 2f 69 70 6c 61 79 65 72 62 65 74 61 2f
65 70 69 73 6f 64 65 2f 62 30 30 63 37 33 66 63
00 00 09
].map{ |b| b.to_i(16) }.pack('C*')

def test_should_extract_elements_from_sample
amf = Net::RTMP::AMF.new
amf.parse(SAMPLE)
expected = [
"connect",
1.0,
{
"capabilities" => 15.0,
"videoFunction" => 1.0,
"audioCodecs" => 1639.0,
"app" =>
"ondemand?_fcs_vhost=cp48184.edgefcs.net&auth=daEcIaKaQdfaicZcBa_aLa9dYbhdCaCc3d9-bizwQB-cCp-FnrDCqBnNDoGuwF&aifp=v001&slist=secure/6music/AMI_e6d01bf639fe37be3a42e423f9f38425_b00c73d2_6m_lamacq_thu",
"videoCodecs" => 252.0,
"swfUrl" => "http://www.bbc.co.uk/emp/player.swf?revision=3704",
"pageUrl" => "http://www.bbc.co.uk/iplayerbeta/episode/b00c73fc",
"tcUrl" =>
"rtmp://84.53.177.140:1935/ondemand?_fcs_vhost=cp48184.edgefcs.net&auth=daEcIaKaQdfaicZcBa_aLa9dYbhdCaCc3d9-bizwQB-cCp-FnrDCqBnNDoGuwF&aifp=v001&slist=secure/6music/AMI_e6d01bf639fe37be3a42e423f9f38425_b00c73d2_6m_lamacq_thu",
"fpad" => false,
"flashVer" => "LNX 9,0,124,0"
}
]
assert_equal expected, amf.to_a
end

end

0 comments on commit b8d15de

Please sign in to comment.