websocket-driver 0.1.0

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,117 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Client < Hybi
5
+ def self.generate_key
6
+ Base64.encode64((1..16).map { rand(255).chr } * '').strip
7
+ end
8
+
9
+ def initialize(socket, options = {})
10
+ super
11
+
12
+ @ready_state = -1
13
+ @key = Client.generate_key
14
+ @accept = Hybi.generate_accept(@key)
15
+ end
16
+
17
+ def version
18
+ 'hybi-13'
19
+ end
20
+
21
+ def start
22
+ return false unless @ready_state == -1
23
+ @socket.write(handshake_request)
24
+ @ready_state = 0
25
+ true
26
+ end
27
+
28
+ def parse(buffer)
29
+ return super if @ready_state > 0
30
+ message = []
31
+ buffer.each_byte do |data|
32
+ case @ready_state
33
+ when 0 then
34
+ @buffer << data
35
+ validate_handshake if @buffer[-4..-1] == [0x0D, 0x0A, 0x0D, 0x0A]
36
+ when 1 then
37
+ message << data
38
+ end
39
+ end
40
+ parse(message) if @ready_state == 1
41
+ end
42
+
43
+ private
44
+
45
+ def handshake_request
46
+ uri = URI.parse(@socket.url)
47
+ host = uri.host + (uri.port ? ":#{uri.port}" : '')
48
+ path = (uri.path == '') ? '/' : uri.path
49
+ query = uri.query ? "?#{uri.query}" : ''
50
+
51
+ headers = [ "GET #{path}#{query} HTTP/1.1",
52
+ "Host: #{host}",
53
+ "Upgrade: websocket",
54
+ "Connection: Upgrade",
55
+ "Sec-WebSocket-Key: #{@key}",
56
+ "Sec-WebSocket-Version: 13"
57
+ ]
58
+
59
+ if @protocols.size > 0
60
+ headers << "Sec-WebSocket-Protocol: #{@protocols * ', '}"
61
+ end
62
+
63
+ (headers + ['', '']).join("\r\n")
64
+ end
65
+
66
+ def fail_handshake(message)
67
+ message = "Error during WebSocket handshake: #{message}"
68
+ emit(:error, ProtocolError.new(message))
69
+ @ready_state = 3
70
+ emit(:close, CloseEvent.new(ERRORS[:protocol_error], message))
71
+ end
72
+
73
+ def validate_handshake
74
+ data = Driver.encode(@buffer)
75
+ @buffer = []
76
+ response = Net::HTTPResponse.read_new(Net::BufferedIO.new(StringIO.new(data)))
77
+
78
+ unless response.code.to_i == 101
79
+ return fail_handshake("Unexpected response code: #{response.code}")
80
+ end
81
+
82
+ upgrade = response['Upgrade'] || ''
83
+ connection = response['Connection'] || ''
84
+ accept = response['Sec-WebSocket-Accept'] || ''
85
+ protocol = response['Sec-WebSocket-Protocol'] || ''
86
+
87
+ if upgrade == ''
88
+ return fail_handshake("'Upgrade' header is missing")
89
+ elsif upgrade.downcase != 'websocket'
90
+ return fail_handshake("'Upgrade' header value is not 'WebSocket'")
91
+ end
92
+
93
+ if connection == ''
94
+ return fail_handshake("'Connection' header is missing")
95
+ elsif connection.downcase != 'upgrade'
96
+ return fail_handshake("'Connection' header value is not 'Upgrade'")
97
+ end
98
+
99
+ unless accept == @accept
100
+ return fail_handshake('Sec-WebSocket-Accept mismatch')
101
+ end
102
+
103
+ unless protocol == ''
104
+ if @protocols.include?(protocol)
105
+ @protocol = protocol
106
+ else
107
+ return fail_handshake('Sec-WebSocket-Protocol mismatch')
108
+ end
109
+ end
110
+
111
+ open
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+
@@ -0,0 +1,93 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Draft75 < Driver
5
+ def initialize(socket, options = {})
6
+ super
7
+ @stage = 0
8
+ end
9
+
10
+ def version
11
+ 'hixie-75'
12
+ end
13
+
14
+ def parse(buffer)
15
+ buffer = buffer.bytes if buffer.respond_to?(:bytes)
16
+
17
+ buffer.each do |data|
18
+ case @stage
19
+ when -1 then
20
+ @body << data
21
+ send_handshake_body
22
+
23
+ when 0 then
24
+ parse_leading_byte(data)
25
+
26
+ when 1 then
27
+ value = (data & 0x7F)
28
+ @length = value + 128 * @length
29
+
30
+ if @closing and @length.zero?
31
+ @ready_state = 3
32
+ emit(:close, CloseEvent.new(nil, nil))
33
+ elsif (0x80 & data) != 0x80
34
+ if @length.zero?
35
+ @stage = 0
36
+ else
37
+ @skipped = 0
38
+ @stage = 2
39
+ end
40
+ end
41
+
42
+ when 2 then
43
+ if data == 0xFF
44
+ emit(:message, MessageEvent.new(Driver.encode(@buffer)))
45
+ @stage = 0
46
+ else
47
+ if @length
48
+ @skipped += 1
49
+ @stage = 0 if @skipped == @length
50
+ else
51
+ @buffer << data
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ def frame(data, type = nil, error_type = nil)
59
+ return queue([data, type, error_type]) if @ready_state == 0
60
+ data = Driver.encode(data)
61
+ frame = ["\x00", data, "\xFF"].map(&Driver.method(:encode)) * ''
62
+ @socket.write(frame)
63
+ true
64
+ end
65
+
66
+ private
67
+
68
+ def handshake_response
69
+ upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
70
+ upgrade << "Upgrade: WebSocket\r\n"
71
+ upgrade << "Connection: Upgrade\r\n"
72
+ upgrade << "WebSocket-Origin: #{@socket.env['HTTP_ORIGIN']}\r\n"
73
+ upgrade << "WebSocket-Location: #{@socket.url}\r\n"
74
+ upgrade << "\r\n"
75
+ upgrade
76
+ end
77
+
78
+ def parse_leading_byte(data)
79
+ if (0x80 & data) == 0x80
80
+ @length = 0
81
+ @stage = 1
82
+ else
83
+ @length = nil
84
+ @skipped = nil
85
+ @buffer = []
86
+ @stage = 2
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+
@@ -0,0 +1,97 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Draft76 < Draft75
5
+ BODY_SIZE = 8
6
+
7
+ def initialize(socket, options = {})
8
+ super
9
+ input = @socket.env['rack.input']
10
+ @stage = -1
11
+ @body = input ? input.read.bytes.to_a : []
12
+ end
13
+
14
+ def version
15
+ 'hixie-76'
16
+ end
17
+
18
+ def start
19
+ return false unless super
20
+ send_handshake_body
21
+ true
22
+ end
23
+
24
+ def close(reason = nil, code = nil)
25
+ return false if @ready_state == 3
26
+ @socket.write("\xFF\x00")
27
+ @ready_state = 3
28
+ emit(:close, CloseEvent.new(nil, nil))
29
+ true
30
+ end
31
+
32
+ private
33
+
34
+ def handshake_response
35
+ upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
36
+ upgrade << "Upgrade: WebSocket\r\n"
37
+ upgrade << "Connection: Upgrade\r\n"
38
+ upgrade << "Sec-WebSocket-Origin: #{@socket.env['HTTP_ORIGIN']}\r\n"
39
+ upgrade << "Sec-WebSocket-Location: #{@socket.url}\r\n"
40
+ upgrade << "\r\n"
41
+ upgrade
42
+ end
43
+
44
+ def handshake_signature
45
+ return nil unless @body.size >= BODY_SIZE
46
+
47
+ head = @body[0...BODY_SIZE].pack('C*')
48
+ head.force_encoding('ASCII-8BIT') if head.respond_to?(:force_encoding)
49
+
50
+ env = @socket.env
51
+
52
+ key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
53
+ value1 = number_from_key(key1) / spaces_in_key(key1)
54
+
55
+ key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
56
+ value2 = number_from_key(key2) / spaces_in_key(key2)
57
+
58
+ Digest::MD5.digest(big_endian(value1) +
59
+ big_endian(value2) +
60
+ head)
61
+ end
62
+
63
+ def send_handshake_body
64
+ return unless signature = handshake_signature
65
+ @socket.write(signature)
66
+ @stage = 0
67
+ open
68
+ parse(@body[BODY_SIZE..-1]) if @body.size > BODY_SIZE
69
+ end
70
+
71
+ def parse_leading_byte(data)
72
+ return super unless data == 0xFF
73
+ @closing = true
74
+ @length = 0
75
+ @stage = 1
76
+ end
77
+
78
+ def number_from_key(key)
79
+ key.scan(/[0-9]/).join('').to_i(10)
80
+ end
81
+
82
+ def spaces_in_key(key)
83
+ key.scan(/ /).size
84
+ end
85
+
86
+ def big_endian(number)
87
+ string = ''
88
+ [24,16,8,0].each do |offset|
89
+ string << (number >> offset & 0xFF).chr
90
+ end
91
+ string
92
+ end
93
+ end
94
+
95
+ end
96
+ end
97
+
@@ -0,0 +1,43 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ module EventEmitter
5
+ def initialize
6
+ @listeners = Hash.new { |h,k| h[k] = [] }
7
+ end
8
+
9
+ def add_listener(event, &listener)
10
+ @listeners[event.to_s] << listener
11
+ end
12
+
13
+ def on(event, &listener)
14
+ add_listener(event, &listener)
15
+ end
16
+
17
+ def remove_listener(event, &listener)
18
+ @listeners[event.to_s].delete(listener)
19
+ end
20
+
21
+ def remove_all_listeners(event = nil)
22
+ if event
23
+ @listeners.delete(event.to_s)
24
+ else
25
+ @listeners.clear
26
+ end
27
+ end
28
+
29
+ def emit(event, *args)
30
+ @listeners[event.to_s].each do |listener|
31
+ listener.call(*args)
32
+ end
33
+ end
34
+
35
+ def listener_count(event)
36
+ list = @listeners[event.to_s]
37
+ list && list.size
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+
@@ -0,0 +1,368 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Hybi < Driver
5
+ root = File.expand_path('../hybi', __FILE__)
6
+ autoload :StreamReader, root + '/stream_reader'
7
+
8
+ def self.generate_accept(key)
9
+ Base64.encode64(Digest::SHA1.digest(key + GUID)).strip
10
+ end
11
+
12
+ GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
13
+
14
+ BYTE = 0b11111111
15
+ FIN = MASK = 0b10000000
16
+ RSV1 = 0b01000000
17
+ RSV2 = 0b00100000
18
+ RSV3 = 0b00010000
19
+ OPCODE = 0b00001111
20
+ LENGTH = 0b01111111
21
+
22
+ OPCODES = {
23
+ :continuation => 0,
24
+ :text => 1,
25
+ :binary => 2,
26
+ :close => 8,
27
+ :ping => 9,
28
+ :pong => 10
29
+ }
30
+
31
+ OPCODE_CODES = OPCODES.values
32
+ FRAGMENTED_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
33
+ OPENING_OPCODES = OPCODES.values_at(:text, :binary)
34
+
35
+ ERRORS = {
36
+ :normal_closure => 1000,
37
+ :going_away => 1001,
38
+ :protocol_error => 1002,
39
+ :unacceptable => 1003,
40
+ :encoding_error => 1007,
41
+ :policy_violation => 1008,
42
+ :too_large => 1009,
43
+ :extension_error => 1010,
44
+ :unexpected_condition => 1011
45
+ }
46
+
47
+ ERROR_CODES = ERRORS.values
48
+ MIN_RESERVED_ERROR = 3000
49
+ MAX_RESERVED_ERROR = 4999
50
+
51
+ def initialize(socket, options = {})
52
+ super
53
+ reset
54
+
55
+ @reader = StreamReader.new
56
+ @stage = 0
57
+ @masking = options[:masking]
58
+ @protocols = options[:protocols] || []
59
+ @protocols = @protocols.strip.split(/\s*,\s*/) if String === @protocols
60
+
61
+ @require_masking = options[:require_masking]
62
+ @ping_callbacks = {}
63
+ end
64
+
65
+ def version
66
+ "hybi-#{@socket.env['HTTP_SEC_WEBSOCKET_VERSION']}"
67
+ end
68
+
69
+ def parse(data)
70
+ data = data.bytes.to_a if data.respond_to?(:bytes)
71
+ @reader.put(data)
72
+ buffer = true
73
+ while buffer
74
+ case @stage
75
+ when 0 then
76
+ buffer = @reader.read(1)
77
+ parse_opcode(buffer[0]) if buffer
78
+
79
+ when 1 then
80
+ buffer = @reader.read(1)
81
+ parse_length(buffer[0]) if buffer
82
+
83
+ when 2 then
84
+ buffer = @reader.read(@length_size)
85
+ parse_extended_length(buffer) if buffer
86
+
87
+ when 3 then
88
+ buffer = @reader.read(4)
89
+ if buffer
90
+ @mask = buffer
91
+ @stage = 4
92
+ end
93
+
94
+ when 4 then
95
+ buffer = @reader.read(@length)
96
+ if buffer
97
+ @payload = buffer
98
+ emit_frame
99
+ @stage = 0
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ def frame(data, type = nil, code = nil)
106
+ return queue([data, type, code]) if @ready_state == 0
107
+ return false unless @ready_state == 1
108
+
109
+ data = data.to_s unless Array === data
110
+ data = Driver.encode(data) if String === data
111
+
112
+ is_text = (String === data)
113
+ opcode = OPCODES[type || (is_text ? :text : :binary)]
114
+ buffer = data.respond_to?(:bytes) ? data.bytes.to_a : data
115
+ insert = code ? 2 : 0
116
+ length = buffer.size + insert
117
+ header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10)
118
+ offset = header + (@masking ? 4 : 0)
119
+ masked = @masking ? MASK : 0
120
+ frame = Array.new(offset)
121
+
122
+ frame[0] = FIN | opcode
123
+
124
+ if length <= 125
125
+ frame[1] = masked | length
126
+ elsif length <= 65535
127
+ frame[1] = masked | 126
128
+ frame[2] = (length >> 8) & BYTE
129
+ frame[3] = length & BYTE
130
+ else
131
+ frame[1] = masked | 127
132
+ frame[2] = (length >> 56) & BYTE
133
+ frame[3] = (length >> 48) & BYTE
134
+ frame[4] = (length >> 40) & BYTE
135
+ frame[5] = (length >> 32) & BYTE
136
+ frame[6] = (length >> 24) & BYTE
137
+ frame[7] = (length >> 16) & BYTE
138
+ frame[8] = (length >> 8) & BYTE
139
+ frame[9] = length & BYTE
140
+ end
141
+
142
+ if code
143
+ buffer = [(code >> 8) & BYTE, code & BYTE] + buffer
144
+ end
145
+
146
+ if @masking
147
+ mask = [rand(256), rand(256), rand(256), rand(256)]
148
+ frame[header...offset] = mask
149
+ buffer = Mask.mask(buffer, mask)
150
+ end
151
+
152
+ frame.concat(buffer)
153
+
154
+ @socket.write(Driver.encode(frame))
155
+ true
156
+ end
157
+
158
+ def text(message)
159
+ frame(message, :text)
160
+ end
161
+
162
+ def binary(message)
163
+ frame(message, :binary)
164
+ end
165
+
166
+ def ping(message = '', &callback)
167
+ @ping_callbacks[message] = callback if callback
168
+ frame(message, :ping)
169
+ end
170
+
171
+ def close(reason = nil, code = nil)
172
+ reason ||= ''
173
+ code ||= ERRORS[:normal_closure]
174
+
175
+ case @ready_state
176
+ when 0 then
177
+ @ready_state = 3
178
+ emit(:close, CloseEvent.new(code, reason))
179
+ true
180
+ when 1 then
181
+ frame(reason, :close, code)
182
+ @ready_state = 2
183
+ true
184
+ else
185
+ false
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ def handshake_response
192
+ sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
193
+ return '' unless String === sec_key
194
+
195
+ accept = Hybi.generate_accept(sec_key)
196
+ protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
197
+ supported = @protocols
198
+ proto = nil
199
+
200
+ headers = [
201
+ "HTTP/1.1 101 Switching Protocols",
202
+ "Upgrade: websocket",
203
+ "Connection: Upgrade",
204
+ "Sec-WebSocket-Accept: #{accept}"
205
+ ]
206
+
207
+ if protos
208
+ protos = protos.split(/\s*,\s*/) if String === protos
209
+ proto = protos.find { |p| supported.include?(p) }
210
+ if proto
211
+ @protocol = proto
212
+ headers << "Sec-WebSocket-Protocol: #{proto}"
213
+ end
214
+ end
215
+
216
+ (headers + ['','']).join("\r\n")
217
+ end
218
+
219
+ def shutdown(code, reason)
220
+ frame(reason, :close, code)
221
+ @ready_state = 3
222
+ emit(:close, CloseEvent.new(code, reason))
223
+ end
224
+
225
+ def fail(type, message)
226
+ emit(:error, ProtocolError.new(message))
227
+ shutdown(ERRORS[type], message)
228
+ end
229
+
230
+ def parse_opcode(data)
231
+ rsvs = [RSV1, RSV2, RSV3].map { |rsv| (data & rsv) == rsv }
232
+
233
+ if rsvs.any?
234
+ return fail(:protocol_error,
235
+ "One or more reserved bits are on: reserved1 = #{rsvs[0] ? 1 : 0}" +
236
+ ", reserved2 = #{rsvs[1] ? 1 : 0 }" +
237
+ ", reserved3 = #{rsvs[2] ? 1 : 0 }")
238
+ end
239
+
240
+ @final = (data & FIN) == FIN
241
+ @opcode = (data & OPCODE)
242
+ @mask = []
243
+ @payload = []
244
+
245
+ unless OPCODES.values.include?(@opcode)
246
+ return fail(:protocol_error, "Unrecognized frame opcode: #{@opcode}")
247
+ end
248
+
249
+ unless FRAGMENTED_OPCODES.include?(@opcode) or @final
250
+ return fail(:protocol_error, "Received fragmented control frame: opcode = #{@opcode}")
251
+ end
252
+
253
+ if @mode and OPENING_OPCODES.include?(@opcode)
254
+ return fail(:protocol_error, 'Received new data frame but previous continuous frame is unfinished')
255
+ end
256
+
257
+ @stage = 1
258
+ end
259
+
260
+ def parse_length(data)
261
+ @masked = (data & MASK) == MASK
262
+ if @require_masking and not @masked
263
+ return fail(:unacceptable, 'Received unmasked frame but masking is required')
264
+ end
265
+
266
+ @length = (data & LENGTH)
267
+
268
+ if @length <= 125
269
+ @stage = @masked ? 3 : 4
270
+ else
271
+ @length_size = (@length == 126) ? 2 : 8
272
+ @stage = 2
273
+ end
274
+ end
275
+
276
+ def parse_extended_length(buffer)
277
+ @length = integer(buffer)
278
+
279
+ unless FRAGMENTED_OPCODES.include?(@opcode) or @length <= 125
280
+ return fail(:protocol_error, "Received control frame having too long payload: #{@length}")
281
+ end
282
+
283
+ @stage = @masked ? 3 : 4
284
+ end
285
+
286
+ def emit_frame
287
+ payload = @masked ? Mask.mask(@payload, @mask) : @payload
288
+
289
+ case @opcode
290
+ when OPCODES[:continuation] then
291
+ return fail(:protocol_error, 'Received unexpected continuation frame') unless @mode
292
+ @buffer.concat(payload)
293
+ if @final
294
+ message = @buffer
295
+ message = Driver.encode(message, true) if @mode == :text
296
+ reset
297
+ if message
298
+ emit(:message, MessageEvent.new(message))
299
+ else
300
+ fail(:encoding_error, 'Could not decode a text frame as UTF-8')
301
+ end
302
+ end
303
+
304
+ when OPCODES[:text] then
305
+ if @final
306
+ message = Driver.encode(payload, true)
307
+ if message
308
+ emit(:message, MessageEvent.new(message))
309
+ else
310
+ fail(:encoding_error, 'Could not decode a text frame as UTF-8')
311
+ end
312
+ else
313
+ @mode = :text
314
+ @buffer.concat(payload)
315
+ end
316
+
317
+ when OPCODES[:binary] then
318
+ if @final
319
+ emit(:message, MessageEvent.new(payload))
320
+ else
321
+ @mode = :binary
322
+ @buffer.concat(payload)
323
+ end
324
+
325
+ when OPCODES[:close] then
326
+ code = (payload.size >= 2) ? 256 * payload[0] + payload[1] : nil
327
+
328
+ unless (payload.size == 0) or
329
+ (code && code >= MIN_RESERVED_ERROR && code <= MAX_RESERVED_ERROR) or
330
+ ERROR_CODES.include?(code)
331
+ code = ERRORS[:protocol_error]
332
+ end
333
+
334
+ if payload.size > 125 or not Driver.valid_utf8?(payload[2..-1] || [])
335
+ code = ERRORS[:protocol_error]
336
+ end
337
+
338
+ reason = (payload.size > 2) ? Driver.encode(payload[2..-1], true) : ''
339
+ shutdown(code, reason || '')
340
+
341
+ when OPCODES[:ping] then
342
+ frame(payload, :pong)
343
+
344
+ when OPCODES[:pong] then
345
+ message = Driver.encode(payload, true)
346
+ callback = @ping_callbacks[message]
347
+ @ping_callbacks.delete(message)
348
+ callback.call if callback
349
+ end
350
+ end
351
+
352
+ def reset
353
+ @buffer = []
354
+ @mode = nil
355
+ end
356
+
357
+ def integer(bytes)
358
+ number = 0
359
+ bytes.each_with_index do |data, i|
360
+ number += data << (8 * (bytes.size - 1 - i))
361
+ end
362
+ number
363
+ end
364
+ end
365
+
366
+ end
367
+ end
368
+