em-rtmp 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ require "em-rtmp/buffer"
2
+ require "em-rtmp/io_helpers"
3
+
4
+ module EventMachine
5
+ module RTMP
6
+ class ConnectionDelegate
7
+ include IOHelpers
8
+
9
+ attr_accessor :state
10
+
11
+ # Initialize the connection delegate by storing a reference to
12
+ # the connection
13
+ #
14
+ # Returns nothing.
15
+ def initialize(connection)
16
+ @connection = connection
17
+ @state = :none
18
+ end
19
+
20
+ # Connection Delegates send read operations directly to the connection
21
+
22
+ # Reads from the connection buffer
23
+ #
24
+ # length - Bytes to read
25
+ #
26
+ # Returns the result of the read
27
+ def read(length)
28
+ @connection.read length
29
+ end
30
+
31
+ # Connection Delegates send write operations directly to the connection
32
+
33
+ # Writes to the connection socket
34
+ #
35
+ # data - Data to write
36
+ #
37
+ # Returns the result of the write
38
+ def write(data)
39
+ @connection.write data
40
+ end
41
+
42
+ # Obtain the number of bytes waiting in the buffer
43
+ #
44
+ # Returns an Integer
45
+ def bytes_waiting
46
+ @connection.bytes_waiting
47
+ end
48
+
49
+ # Used to track changes in state
50
+ #
51
+ # Returns nothing
52
+ def change_state(state)
53
+ return if @state == state
54
+ Logger.info "state changed from #{@state} to #{state}", caller: caller
55
+ @state = state
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,94 @@
1
+ require "em-rtmp/connection_delegate"
2
+
3
+ module EventMachine
4
+ module RTMP
5
+ class Handshake < ConnectionDelegate
6
+
7
+ # The RTMP handshake involes each party sending 3 packets of data.
8
+ #
9
+ # Client Server
10
+ # --------------------------------------------
11
+ # 0x03 ->
12
+ # 1536 random bytes (a) ->
13
+ # <- 0x03
14
+ # <- 1536 random bytes (b)
15
+ # b ->
16
+ # <- a
17
+ #
18
+ # The handshake is completed by verifying that the response received
19
+ # matches the challenge string, with the notable exception that the
20
+ # first 4 bytes may be used for timestamping and can be different, and
21
+ # the second 4 bytes must all be zero.
22
+
23
+ HANDSHAKE_VERSION = 0x03
24
+ HANDSHAKE_LENGTH = 1536
25
+
26
+ # Handles a change to the buffer state
27
+ #
28
+ # Returns a symbol indicating our state
29
+ def buffer_changed
30
+ Logger.debug "#{state} #{bytes_waiting}"
31
+ case state
32
+ when :challenge_issued
33
+ if bytes_waiting >= (1 + HANDSHAKE_LENGTH)
34
+ handle_server_challenge
35
+ end
36
+ when :challenge_received
37
+ if bytes_waiting >= HANDSHAKE_LENGTH
38
+ handle_server_response
39
+ end
40
+ else
41
+ raise HandshakeError, "Reached unexpected state"
42
+ end
43
+
44
+ state
45
+ end
46
+
47
+ # Send the version byte followed by our challenge
48
+ #
49
+ # Returns a state update
50
+ def issue_challenge
51
+ Logger.debug "issuing client challenge"
52
+
53
+ @client_challenge = "\x00\x00\x00\x00\x00\x00\x00\x00" + (8...HANDSHAKE_LENGTH).map{rand(255)}.pack('C*')
54
+
55
+ write_uint8 HANDSHAKE_VERSION
56
+ write @client_challenge
57
+
58
+ change_state :challenge_issued
59
+ end
60
+
61
+ # Receives the server version byte and reissues it to the stream
62
+ #
63
+ # Returns a state update
64
+ def handle_server_challenge
65
+ Logger.debug "handling server challenge"
66
+
67
+ server_version = read_uint8
68
+ unless server_version == HANDSHAKE_VERSION
69
+ raise HandshakeError, "Expected version byte to be 0x03"
70
+ end
71
+
72
+ server_challenge = read(HANDSHAKE_LENGTH)
73
+ write server_challenge
74
+ change_state :challenge_received
75
+ end
76
+
77
+ # Reads the server response to our challenge to authenticate peer
78
+ #
79
+ # Returns a state update
80
+ def handle_server_response
81
+ Logger.debug "handling client response"
82
+
83
+ server_response = read(HANDSHAKE_LENGTH)
84
+ unless server_response == @client_challenge
85
+ raise HandshakeError, "Expected server to return client challenge"
86
+ end
87
+
88
+ Logger.debug "handshake complete"
89
+ change_state :handshake_complete
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,193 @@
1
+ module EventMachine
2
+ module RTMP
3
+ class Header
4
+
5
+ # The packet header is read as follows:
6
+ #
7
+ # The first byte (8 bits) is read into two values:
8
+ # Header type (2 bits)
9
+ # Stream ID (6 bits)
10
+ #
11
+ # The header type determines a header length, from which the rest of the data may be read:
12
+ # TYPE BYTES DESCRIPTION
13
+ # 0b11 1 Just the header
14
+ # 0b10 4 Above plus timestamp (uint24 big endian)
15
+ # 0b01 8 Above plus body length (uint24 big endian) and message id (uint8 big endian)
16
+ # 0b00 12 Above plus Message stream id (uint32 little endian)
17
+
18
+ attr_accessor :header_length, :body_length, :channel_id,
19
+ :message_type_id, :message_stream_id, :timestamp
20
+
21
+ HEADER_DEFAULTS = {
22
+ header_length: 12,
23
+ timestamp: 0,
24
+ channel_id: 3,
25
+ message_type_id: 0,
26
+ message_stream_id: 0
27
+ }
28
+
29
+ # RTMP has a variable length header
30
+ # The keys below are binary values which correspond to expected byte lengths
31
+ # (0b00 in header length field would result in an expected 12 byte header)
32
+ HEADER_LENGTHS = {
33
+ 0b00 => 12,
34
+ 0b01 => 8,
35
+ 0b10 => 4,
36
+ 0b11 => 1
37
+ }
38
+
39
+ # RTMP uses a single byte to represent the message type
40
+ # These are the known values.
41
+ MESSAGE_TYPES = {
42
+ 0x00 => :none,
43
+ 0x01 => :chunk_size,
44
+ 0x02 => :abort,
45
+ 0x03 => :ack,
46
+ 0x04 => :ping,
47
+ 0x05 => :ack_size,
48
+ 0x06 => :bandwidth,
49
+ 0x08 => :audio,
50
+ 0x09 => :video,
51
+ 0x0f => :flex, # aka AMF3 data
52
+ 0x10 => :amf3_shared_object, # documented as kMsgContainer=16
53
+ 0x11 => :amf3,
54
+ 0x12 => :invoke, # aka AMF0 data
55
+ 0x13 => :amf0_shared_object, # documented as kMsgContainer=19
56
+ 0x14 => :amf0,
57
+ 0x16 => :flv # documented as aggregate
58
+ }
59
+
60
+ # Initialize and set instance variables
61
+ #
62
+ # attrs - Hash of instance variables
63
+ #
64
+ # Returns nothing
65
+ def initialize(attrs={})
66
+ attrs.each {|k,v| send("#{k}=", v)}
67
+ end
68
+
69
+ # Inherit values from another header
70
+ #
71
+ # h - other Header object
72
+ #
73
+ # Returns self
74
+ def +(header)
75
+ keys = %w[header_length body_length channel_id message_type_id message_stream_id timestamp]
76
+ other_values = Hash[keys.map {|k| [k, header.instance_variable_get("@#{k}")]}]
77
+ other_values.each do |k, v|
78
+ unless v.nil?
79
+ send("#{k}=", v) unless v.nil?
80
+ end
81
+ end
82
+ self
83
+ end
84
+
85
+ # Retrieve the message type for our header
86
+ #
87
+ # Returns a symbol or nil
88
+ def message_type
89
+ MESSAGE_TYPES[message_type_id] || "unknown_type_#{message_type_id}".to_sym
90
+ end
91
+
92
+ # Set message type as symbol
93
+ #
94
+ # type - message type symbol
95
+ #
96
+ # Returns the id set
97
+ def message_type=(type)
98
+ self.message_type_id = MESSAGE_TYPES.invert[type]
99
+ end
100
+
101
+ # Encode the instantiated header to a buffer
102
+ #
103
+ # io - IO destination to write to
104
+ #
105
+ # Returns the buffer
106
+ def encode
107
+
108
+ # Set defaults if necessary
109
+ HEADER_DEFAULTS.each do |k, v|
110
+ send("#{k}=", v) if instance_variable_get("@#{k}").nil?
111
+ end
112
+
113
+ h_type = HEADER_LENGTHS.invert[header_length]
114
+
115
+ buffer = Buffer.new
116
+ buffer.write_bitfield [h_type, 2], [channel_id, 6]
117
+
118
+ if header_length >= 4
119
+ buffer.write_uint24_be timestamp
120
+ end
121
+
122
+ if header_length >= 8
123
+ buffer.write_uint24_be body_length
124
+ buffer.write_uint8 message_type_id
125
+ end
126
+
127
+ if header_length == 12
128
+ buffer.write_uint32_le message_stream_id
129
+ end
130
+
131
+ buffer.string
132
+ end
133
+
134
+ # Read the header from a connection
135
+ #
136
+ # connection - connection to use
137
+ #
138
+ # Returns a new header instance
139
+ def self.read_from_connection(stream)
140
+ header = new
141
+
142
+ begin
143
+ h_type, header.channel_id = stream.read_bitfield(2, 6)
144
+ rescue => e
145
+ raise HeaderError, "Unable to read header type byte from buffer: #{e}"
146
+ end
147
+
148
+ unless header.header_length = HEADER_LENGTHS[h_type]
149
+ raise HeaderError, "invalid header type #{h_type}"
150
+ end
151
+
152
+ # Stream ID may occupy up to two more bytes depending on the
153
+ # value of the channel_id we have read:
154
+ # 0 - value is second byte + 64
155
+ # 1 - value is third byte * 256 + second byte + 64
156
+ # 2 - low level protocol message (ignore)
157
+
158
+ if header.channel_id == 0x00
159
+ header.channel_id = stream.read_uint8 + 64
160
+ elsif header.channel_id == 0x01
161
+ header.channel_id = stream.read_uint8 + 64 + (stream.read_uint8 * 256)
162
+ end
163
+
164
+ # The timestamp is a 3-byte uint24. If this matches 0xffffff we will use another 4 bytes
165
+ # after the header as the real timestamp.
166
+ if header.header_length >= 4
167
+ header.timestamp = stream.read_uint24_be
168
+ end
169
+
170
+ # The next 3 bytes are the length of the object body, followed by a single byte
171
+ # representing the content type
172
+ if header.header_length >= 8
173
+ header.body_length = stream.read_uint24_be
174
+ header.message_type_id = stream.read_uint8
175
+ end
176
+
177
+ # The next 4 bytes are the stream ID
178
+ if header.header_length >= 12
179
+ header.message_stream_id = stream.read_uint32_le
180
+ end
181
+
182
+ # If the timestamp was 0xffffff, the next 4 bytes are the real timestamp
183
+ if header.timestamp == 0xffffff
184
+ header.timestamp = stream.read_uint32_be
185
+ end
186
+
187
+ header
188
+ end
189
+
190
+ end
191
+
192
+ end
193
+ end
@@ -0,0 +1,36 @@
1
+ module EventMachine
2
+ module RTMP
3
+ class Heartbeat < ConnectionDelegate
4
+
5
+ def initialize(connection)
6
+ super connection
7
+ end
8
+
9
+ def buffer_changed
10
+
11
+ end
12
+
13
+ def start
14
+ @timer ||= EventMachine::PeriodicTimer.new(15) do
15
+ pulse
16
+ end
17
+
18
+ @block = Proc.new do
19
+ Logger.debug "Heartbeat Pulsing"
20
+ end
21
+
22
+ @block.call
23
+ end
24
+
25
+ def pulse
26
+ @block.call
27
+ end
28
+
29
+ def cancel
30
+ @timer.cancel
31
+ @timer = nil
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,192 @@
1
+ module EventMachine
2
+ module RTMP
3
+ module IOHelpers
4
+
5
+ # Read a unsigned 8-bit integer
6
+ #
7
+ # Returns the result of the read
8
+ def read_uint8
9
+ read(1).unpack('C')[0]
10
+ end
11
+
12
+ # Write an unsigned 8-bit integer
13
+ #
14
+ # value - Value to write
15
+ #
16
+ # Returns the result of the stream operation
17
+ def write_uint8(value)
18
+ write [value].pack('C')
19
+ end
20
+
21
+ # Read a unsigned 16-bit integer
22
+ #
23
+ # Returns the result of the read
24
+ def read_uint16_be
25
+ read(2).unpack('n')[0]
26
+ end
27
+
28
+ # Write an unsigned 16-bit integer
29
+ #
30
+ # value - Value to write
31
+ #
32
+ # Returns the result of the stream operation
33
+ def write_uint16_be(value)
34
+ write [value].pack('n')
35
+ end
36
+
37
+ # Read a unsigned 24-bit integer
38
+ #
39
+ # Returns the result of the read
40
+ def read_uint24_be
41
+ ("\x00" + read(3)).unpack('N')[0]
42
+ end
43
+
44
+ # Write an unsigned 24-bit integer
45
+ #
46
+ # value - Value to write
47
+ #
48
+ # Returns the result of the stream operation
49
+ def write_uint24_be(value)
50
+ write [value].pack('N')[1,3]
51
+ end
52
+
53
+ # Read a unsigned 32-bit integer (big endian)
54
+ #
55
+ # Returns the result of the read
56
+ def read_uint32_be
57
+ read(4).unpack('N')[0]
58
+ end
59
+
60
+ # Read a unsigned 32-bit integer (little endian)
61
+ #
62
+ # Returns the result of the read
63
+ def read_uint32_le
64
+ read(4).unpack('V')[0]
65
+ end
66
+
67
+ # Write an unsigned 32-bit integer (big endian)
68
+ #
69
+ # value - Value to write
70
+ #
71
+ # Returns the result of the stream operation
72
+ def write_uint32_be(value)
73
+ write [value].pack('N')
74
+ end
75
+
76
+ # Write an unsigned 32-bit integer (big endian)
77
+ #
78
+ # value - Value to write
79
+ #
80
+ # Returns the result of the stream operation
81
+ def write_uint32_le(value)
82
+ write [value].pack('V')
83
+ end
84
+
85
+ # Read a double (big endian)
86
+ #
87
+ # Returns the result of the read
88
+ def read_double_be
89
+ read(8).unpack('G')[0]
90
+ end
91
+
92
+ # Write a double (big endian)
93
+ #
94
+ # value - Value to write
95
+ #
96
+ # Returns the result of the stream operation
97
+ def write_double_be(value)
98
+ write [value].pack('G')
99
+ end
100
+
101
+ # Read an int29
102
+ #
103
+ # Returns the result of the stream operation
104
+ def read_int29
105
+ count = 1
106
+ result = 0
107
+ byte = read_uint8
108
+
109
+ while (byte & 0x80 != 0) && count < 4 do
110
+ result <<= 7
111
+ result |= (byte & 0x7f)
112
+ byte = read_uint8
113
+ count += 1
114
+ end
115
+
116
+ if count < 4
117
+ result <<= 7
118
+ result |= byte
119
+ else
120
+ result <<= 8
121
+ result |= byte
122
+ end
123
+
124
+ result
125
+ end
126
+
127
+ # Write an int29
128
+ #
129
+ # value - Value to write
130
+ #
131
+ # Returns the result of the stream operation
132
+ def write_int29(value)
133
+ value = value & 0x1fffffff
134
+ if(value < 0x80)
135
+ result = [value].pack('c')
136
+ write result
137
+ elsif(value < 0x4000)
138
+ result = [value >> 7 & 0x7f | 0x80].pack('c') + [value & 0x7f].pack('c')
139
+ write result
140
+ elsif(value < 0x200000)
141
+ result = [value >> 14 & 0x7f | 0x80].pack('c') + [value >> 7 & 0x7f | 0x80].pack('c') + [value & 0x7f].pack('c')
142
+ write result
143
+ else
144
+ result = [value >> 22 & 0x7f | 0x80].pack('c') + [value >> 15 & 0x7f | 0x80].pack('c') + [value >> 8 & 0x7f | 0x80].pack('c') + [value & 0xff].pack('c')
145
+ write result
146
+ end
147
+ end
148
+
149
+ # Read a bit field and return the mapped results
150
+ #
151
+ # widths - Array of integers representing the size of the fields
152
+ #
153
+ # Returns the value for each field read
154
+ def read_bitfield(*widths)
155
+ byte = read_uint8
156
+ shifts_and_masks(widths).map{ |shift, mask|
157
+ (byte >> shift) & mask
158
+ }
159
+ end
160
+
161
+ # Write a bit field to the stream
162
+ #
163
+ # values_and_widths - An array of arrays, each containing two values:
164
+ # [0] - value to be written
165
+ # [1] - width of value
166
+ #
167
+ # Returns the value for each field read
168
+ def write_bitfield(*values_and_widths)
169
+ sm = shifts_and_masks(values_and_widths.map{ |_,w| w })
170
+ write_uint8 values_and_widths.zip(sm).inject(0){ |byte, ((value, width), (shift, mask))|
171
+ byte | ((value & mask) << shift)
172
+ }
173
+ end
174
+
175
+ private
176
+
177
+ # Obtain the shift and bitmasks for a set of widths
178
+ #
179
+ # Returns an array of arrays, each top-level array containing
180
+ # two values:
181
+ # [0] - shift
182
+ # [1] - mask
183
+ def shifts_and_masks(bit_widths)
184
+ (0 ... bit_widths.length).map{ |i| [
185
+ bit_widths[i+1 .. -1].inject(0){ |a,e| a + e },
186
+ 0b1111_1111 >> (8 - bit_widths[i])
187
+ ]}
188
+ end
189
+
190
+ end
191
+ end
192
+ end