em-rtmp 0.0.3

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.
@@ -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