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.
- data/CHANGELOG.md +6 -0
- data/README.md +266 -0
- data/ext/websocket_mask/WebsocketMaskService.java +61 -0
- data/ext/websocket_mask/extconf.rb +5 -0
- data/ext/websocket_mask/websocket_mask.c +33 -0
- data/lib/websocket/driver.rb +158 -0
- data/lib/websocket/driver/client.rb +117 -0
- data/lib/websocket/driver/draft75.rb +93 -0
- data/lib/websocket/driver/draft76.rb +97 -0
- data/lib/websocket/driver/event_emitter.rb +43 -0
- data/lib/websocket/driver/hybi.rb +368 -0
- data/lib/websocket/driver/hybi/stream_reader.rb +30 -0
- data/lib/websocket/driver/utf8_match.rb +7 -0
- metadata +95 -0
@@ -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
|
+
|