faye-websocket 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of faye-websocket might be problematic. Click here for more details.

@@ -0,0 +1,22 @@
1
+ === 0.2.0 / 2011-12-21
2
+
3
+ * Add support for Sec-WebSocket-Protocol negotiation
4
+ * Support hixie-76 close frames and 75/76 ignored segments
5
+ * Improve performance of HyBi parsing/framing functions
6
+ * Write masking function in C
7
+
8
+
9
+ === 0.1.2 / 2011-12-05
10
+
11
+ * Make hixie-76 sockets work through HAProxy
12
+
13
+
14
+ === 0.1.1 / 2011-11-30
15
+
16
+ * Fix add_event_listener() interface methods
17
+
18
+
19
+ === 0.1.0 / 2011-11-27
20
+
21
+ * Initial release, based on WebSocket components from Faye
22
+
@@ -59,8 +59,8 @@ is how you'd implement an echo server:
59
59
 
60
60
  The client supports both the plain-text +ws+ protocol and the encrypted +wss+
61
61
  protocol, and has exactly the same interface as a socket you would use in a web
62
- browser. On the wire it identifies itself as hybi-08, though it's compatible
63
- with servers speaking later versions of the protocol, at least up to version 17.
62
+ browser. On the wire it identifies itself as hybi-13, though it's compatible
63
+ with servers speaking later versions of the protocol.
64
64
 
65
65
  require 'faye/websocket'
66
66
  require 'eventmachine'
@@ -84,6 +84,26 @@ with servers speaking later versions of the protocol, at least up to version 17.
84
84
  }
85
85
 
86
86
 
87
+ == Subprotocol negotiation
88
+
89
+ The WebSocket protocol allows peers to select and identify the application
90
+ protocol to use over the connection. On the client side, you can set which
91
+ protocols the client accepts by passing a list of protocol names when you
92
+ construct the socket:
93
+
94
+ ws = Faye::WebSocket::Client.new('ws://www.example.com/', ['irc', 'amqp'])
95
+
96
+ On the server side, you can likewise pass in the list of protocols the server
97
+ supports after the other constructor arguments:
98
+
99
+ ws = Faye::WebSocket.new(env, ['irc', 'amqp'])
100
+
101
+ If the client and server agree on a protocol, both the client- and server-side
102
+ socket objects expose the selected protocol through the <tt>ws.protocol</tt>
103
+ property. If they cannot agree on a protocol to use, the client closes the
104
+ connection.
105
+
106
+
87
107
  == WebSocket API
88
108
 
89
109
  The WebSocket API consists of several event handlers and a method for sending
@@ -105,6 +125,8 @@ messages.
105
125
  the other peer.
106
126
  * <b><tt>close(code, reason)</tt></b> closes the connection, sending the given
107
127
  status code and reason text, both of which are optional.
128
+ * <b><tt>protocol</tt></b> is a string or +nil+ identifying the subprotocol th
129
+ socket is using.
108
130
 
109
131
 
110
132
  == License
@@ -6,8 +6,7 @@ EM.run {
6
6
  host = 'ws://localhost:9001'
7
7
  agent = "Faye (Ruby #{RUBY_VERSION})"
8
8
  cases = 0
9
- skip = [247,248,249,250,251,252,253,254,255,
10
- 256,257,258,259,260,261,262,263,264]
9
+ skip = []
11
10
 
12
11
  socket = Faye::WebSocket::Client.new("#{host}/getCaseCount")
13
12
 
@@ -12,14 +12,19 @@
12
12
  <script type="text/javascript">
13
13
  var logger = document.getElementsByTagName('ul')[0],
14
14
  Socket = window.MozWebSocket || window.WebSocket,
15
- socket = new Socket('ws://' + location.hostname + ':' + location.port + '/'),
15
+ protos = ['foo', 'bar', 'xmpp'],
16
+ socket = new Socket('ws://' + location.hostname + ':' + location.port + '/', protos),
16
17
  index = 0;
17
18
 
18
19
  socket.onopen = function() {
19
- logger.innerHTML += '<li>OPEN</li>';
20
+ logger.innerHTML += '<li>OPEN: ' + socket.protocol + '</li>';
20
21
  socket.send('Hello, world');
21
22
  };
22
23
 
24
+ socket.onerror = function(event) {
25
+ logger.innerHTML += '<li>ERROR: ' + error.message + '</li>';
26
+ };
27
+
23
28
  socket.addEventListener('message', function(event) {
24
29
  logger.innerHTML += '<li>MESSAGE: ' + event.data + '</li>';
25
30
  setTimeout(function() { socket.send(++index + ' ' + event.data) }, 2000);
@@ -10,8 +10,8 @@ static = Rack::File.new(File.dirname(__FILE__))
10
10
 
11
11
  app = lambda do |env|
12
12
  if env['HTTP_UPGRADE']
13
- socket = Faye::WebSocket.new(env)
14
- p [:open, socket.url, socket.version]
13
+ socket = Faye::WebSocket.new(env, ['irc', 'xmpp'])
14
+ p [:open, socket.url, socket.version, socket.protocol]
15
15
 
16
16
  socket.onmessage = lambda do |event|
17
17
  socket.send(event.data)
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ extension_name = 'faye_websocket_mask'
4
+ dir_config(extension_name)
5
+ create_makefile(extension_name)
6
+
@@ -0,0 +1,35 @@
1
+ #include <ruby.h>
2
+
3
+ VALUE Faye = Qnil;
4
+ VALUE FayeWebSocket = Qnil;
5
+ VALUE FayeWebSocketMask = Qnil;
6
+
7
+ void Init_faye_websocket_mask();
8
+ VALUE method_faye_websocket_mask(VALUE self, VALUE payload, VALUE mask);
9
+
10
+ void Init_faye_websocket_mask() {
11
+ Faye = rb_define_module("Faye");
12
+ FayeWebSocket = rb_define_class_under(Faye, "WebSocket", rb_cObject);
13
+ FayeWebSocketMask = rb_define_module_under(FayeWebSocket, "Mask");
14
+ rb_define_singleton_method(FayeWebSocketMask, "mask", method_faye_websocket_mask, 2);
15
+ }
16
+
17
+ VALUE method_faye_websocket_mask(VALUE self, VALUE payload, VALUE mask) {
18
+ int n = RARRAY_LEN(payload), i, p, m;
19
+ VALUE unmasked = rb_ary_new2(n);
20
+
21
+ int mask_array[] = {
22
+ NUM2INT(rb_ary_entry(mask, 0)),
23
+ NUM2INT(rb_ary_entry(mask, 1)),
24
+ NUM2INT(rb_ary_entry(mask, 2)),
25
+ NUM2INT(rb_ary_entry(mask, 3))
26
+ };
27
+
28
+ for (i = 0; i < n; i++) {
29
+ p = NUM2INT(rb_ary_entry(payload, i));
30
+ m = mask_array[i % 4];
31
+ rb_ary_store(unmasked, i, INT2NUM(p ^ m));
32
+ }
33
+ return unmasked;
34
+ }
35
+
@@ -22,12 +22,13 @@ module Faye
22
22
  class WebSocket
23
23
 
24
24
  root = File.expand_path('../websocket', __FILE__)
25
+ require root + '/../../faye_websocket_mask'
25
26
 
26
27
  autoload :API, root + '/api'
27
28
  autoload :Client, root + '/client'
28
29
  autoload :Draft75Parser, root + '/draft75_parser'
29
30
  autoload :Draft76Parser, root + '/draft76_parser'
30
- autoload :Protocol8Parser, root + '/protocol8_parser'
31
+ autoload :HybiParser, root + '/hybi_parser'
31
32
 
32
33
  # http://www.w3.org/International/questions/qa-forms-utf-8.en.php
33
34
  UTF8_MATCH = /^([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$/
@@ -47,7 +48,7 @@ module Faye
47
48
 
48
49
  def self.parser(env)
49
50
  if env['HTTP_SEC_WEBSOCKET_VERSION']
50
- Protocol8Parser
51
+ HybiParser
51
52
  elsif env['HTTP_SEC_WEBSOCKET_KEY1']
52
53
  Draft76Parser
53
54
  else
@@ -61,7 +62,7 @@ module Faye
61
62
  attr_reader :env
62
63
  include API
63
64
 
64
- def initialize(env)
65
+ def initialize(env, supported_protos = nil)
65
66
  @env = env
66
67
  @callback = @env['async.callback']
67
68
  @stream = Stream.new(self, @env['em.connection'])
@@ -71,7 +72,7 @@ module Faye
71
72
  @ready_state = CONNECTING
72
73
  @buffered_amount = 0
73
74
 
74
- @parser = WebSocket.parser(@env).new(self)
75
+ @parser = WebSocket.parser(@env).new(self, :protocols => supported_protos)
75
76
  @stream.write(@parser.handshake_response)
76
77
 
77
78
  @ready_state = OPEN
@@ -86,6 +87,10 @@ module Faye
86
87
  end
87
88
  end
88
89
 
90
+ def protocol
91
+ @parser.protocol || ''
92
+ end
93
+
89
94
  private
90
95
 
91
96
  def determine_url
@@ -3,13 +3,14 @@ module Faye
3
3
 
4
4
  class Client
5
5
  include API
6
- attr_reader :uri
6
+ attr_reader :protocol, :uri
7
7
 
8
- def initialize(url)
9
- @parser = Protocol8Parser.new(self, :masking => true)
8
+ def initialize(url, protocols = nil)
9
+ @parser = HybiParser.new(self, :masking => true, :protocols => protocols)
10
10
  @url = url
11
11
  @uri = URI.parse(url)
12
12
 
13
+ @protocol = ''
13
14
  @ready_state = CONNECTING
14
15
  @buffered_amount = 0
15
16
 
@@ -39,6 +40,7 @@ module Faye
39
40
  return unless @handshake.complete?
40
41
 
41
42
  if @handshake.valid?
43
+ @protocol = @handshake.protocol || ''
42
44
  @ready_state = OPEN
43
45
  event = Event.new('open')
44
46
  event.init_event('open', false, false)
@@ -47,8 +49,8 @@ module Faye
47
49
  receive_data(@message)
48
50
  else
49
51
  @ready_state = CLOSED
50
- event = Event.new('error')
51
- event.init_event('error', false, false)
52
+ event = Event.new('close', :code => 1006, :reason => '')
53
+ event.init_event('close', false, false)
52
54
  dispatch_event(event)
53
55
  end
54
56
 
@@ -2,14 +2,15 @@ module Faye
2
2
  class WebSocket
3
3
 
4
4
  class Draft75Parser
5
- def initialize(web_socket)
6
- @socket = web_socket
7
- @buffer = []
8
- @buffering = false
5
+ attr_reader :protocol
6
+
7
+ def initialize(web_socket, options = {})
8
+ @socket = web_socket
9
+ @stage = 0
9
10
  end
10
11
 
11
12
  def version
12
- 'draft-75'
13
+ 'hixie-75'
13
14
  end
14
15
 
15
16
  def handshake_response
@@ -22,30 +23,58 @@ module Faye
22
23
  upgrade
23
24
  end
24
25
 
25
- def parse(data)
26
- data.each_byte(&method(:handle_byte))
26
+ def parse(buffer)
27
+ buffer.each_byte do |data|
28
+ case @stage
29
+ when 0 then
30
+ parse_leading_byte(data)
31
+
32
+ when 1 then
33
+ value = (data & 0x7F)
34
+ @length = value + 128 * @length
35
+
36
+ if @closing and @length.zero?
37
+ @socket.close(nil, nil, false)
38
+ elsif (0x80 & data) != 0x80
39
+ if @length.zero?
40
+ @socket.receive('')
41
+ @stage = 0
42
+ else
43
+ @buffer = []
44
+ @stage = 2
45
+ end
46
+ end
47
+
48
+ when 2 then
49
+ if data == 0xFF
50
+ @socket.receive(WebSocket.encode(@buffer))
51
+ @stage = 0
52
+ else
53
+ @buffer << data
54
+ if @length and @buffer.size == @length
55
+ @stage = 0
56
+ end
57
+ end
58
+ end
59
+ end
60
+
27
61
  nil
28
62
  end
29
63
 
30
- def frame(data, type = nil, error_type = nil)
31
- ["\x00", data, "\xFF"].map(&WebSocket.method(:encode)) * ''
64
+ def parse_leading_byte(data)
65
+ if (0x80 & data) == 0x80
66
+ @length = 0
67
+ @stage = 1
68
+ else
69
+ @length = nil
70
+ @buffer = []
71
+ @stage = 2
72
+ end
32
73
  end
33
74
 
34
- private
35
-
36
- def handle_byte(data)
37
- case data
38
- when 0x00 then
39
- @buffering = true
40
-
41
- when 0xFF then
42
- @socket.receive(WebSocket.encode(@buffer))
43
- @buffer = []
44
- @buffering = false
45
-
46
- else
47
- @buffer.push(data) if @buffering
48
- end
75
+ def frame(data, type = nil, error_type = nil)
76
+ return WebSocket.encode(data) if Array === data
77
+ ["\x00", data, "\xFF"].map(&WebSocket.method(:encode)) * ''
49
78
  end
50
79
  end
51
80
 
@@ -3,7 +3,7 @@ module Faye
3
3
 
4
4
  class Draft76Parser < Draft75Parser
5
5
  def version
6
- 'draft-76'
6
+ 'hixie-76'
7
7
  end
8
8
 
9
9
  def handshake_response
@@ -42,8 +42,22 @@ module Faye
42
42
  handshake_signature(data)
43
43
  end
44
44
 
45
+ def close(code = nil, reason = nil, &callback)
46
+ return if @closed
47
+ @socket.send([0xFF, 0x00]) if @closing
48
+ @closed = true
49
+ callback.call if callback
50
+ end
51
+
45
52
  private
46
53
 
54
+ def parse_leading_byte(data)
55
+ return super unless data == 0xFF
56
+ @closing = true
57
+ @length = 0
58
+ @stage = 1
59
+ end
60
+
47
61
  def number_from_key(key)
48
62
  key.scan(/[0-9]/).join('').to_i(10)
49
63
  end
@@ -1,16 +1,19 @@
1
1
  module Faye
2
2
  class WebSocket
3
-
4
- class Protocol8Parser
5
- GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
6
-
3
+
4
+ class HybiParser
5
+ root = File.expand_path('../hybi_parser', __FILE__)
6
+ autoload :Handshake, root + '/handshake'
7
+ autoload :StreamReader, root + '/stream_reader'
8
+
9
+ BYTE = 0b11111111
7
10
  FIN = MASK = 0b10000000
8
11
  RSV1 = 0b01000000
9
12
  RSV2 = 0b00100000
10
13
  RSV3 = 0b00010000
11
14
  OPCODE = 0b00001111
12
15
  LENGTH = 0b01111111
13
-
16
+
14
17
  OPCODES = {
15
18
  :continuation => 0,
16
19
  :text => 1,
@@ -19,10 +22,10 @@ module Faye
19
22
  :ping => 9,
20
23
  :pong => 10
21
24
  }
22
-
25
+
23
26
  FRAGMENTED_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
24
27
  OPENING_OPCODES = OPCODES.values_at(:text, :binary)
25
-
28
+
26
29
  ERRORS = {
27
30
  :normal_closure => 1000,
28
31
  :going_away => 1001,
@@ -33,214 +36,201 @@ module Faye
33
36
  :too_large => 1009,
34
37
  :extension_error => 1010
35
38
  }
36
-
39
+
37
40
  ERROR_CODES = ERRORS.values
38
-
39
- class Handshake
40
- def initialize(uri)
41
- @uri = uri
42
- @key = Base64.encode64((1..16).map { rand(255).chr } * '').strip
43
- @accept = Base64.encode64(Digest::SHA1.digest(@key + GUID)).strip
44
- @buffer = []
45
- end
46
-
47
- def request_data
48
- hostname = @uri.host + (@uri.port ? ":#{@uri.port}" : '')
49
-
50
- handshake = "GET #{@uri.path}#{@uri.query ? '?' : ''}#{@uri.query} HTTP/1.1\r\n"
51
- handshake << "Host: #{hostname}\r\n"
52
- handshake << "Upgrade: websocket\r\n"
53
- handshake << "Connection: Upgrade\r\n"
54
- handshake << "Sec-WebSocket-Key: #{@key}\r\n"
55
- handshake << "Sec-WebSocket-Version: 8\r\n"
56
- handshake << "\r\n"
57
-
58
- handshake
59
- end
60
-
61
- def parse(data)
62
- message = []
63
- complete = false
64
- data.each_byte do |byte|
65
- if complete
66
- message << byte
67
- else
68
- @buffer << byte
69
- complete ||= complete?
70
- end
71
- end
72
- message
73
- end
74
-
75
- def complete?
76
- @buffer[-4..-1] == [0x0D, 0x0A, 0x0D, 0x0A]
77
- end
78
-
79
- def valid?
80
- data = WebSocket.encode(@buffer)
81
- response = Net::HTTPResponse.read_new(Net::BufferedIO.new(StringIO.new(data)))
82
- return false unless response.code.to_i == 101
83
-
84
- upgrade, connection = response['Upgrade'], response['Connection']
85
-
86
- upgrade and upgrade =~ /^websocket$/i and
87
- connection and connection.split(/\s*,\s*/).include?('Upgrade') and
88
- response['Sec-WebSocket-Accept'] == @accept
89
- end
90
- end
91
-
41
+
42
+ attr_reader :protocol
43
+
92
44
  def initialize(web_socket, options = {})
93
45
  reset
94
- @socket = web_socket
95
- @stage = 0
96
- @masking = options[:masking]
46
+ @socket = web_socket
47
+ @reader = StreamReader.new
48
+ @stage = 0
49
+ @masking = options[:masking]
50
+ @protocols = options[:protocols]
51
+
52
+ @protocols = @protocols.split(/\s*,\s*/) if String === @protocols
97
53
  end
98
-
54
+
99
55
  def version
100
- "protocol-#{@socket.env['HTTP_SEC_WEBSOCKET_VERSION']}"
56
+ "hybi-#{@socket.env['HTTP_SEC_WEBSOCKET_VERSION']}"
101
57
  end
102
-
58
+
103
59
  def handshake_response
104
60
  sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
105
61
  return '' unless String === sec_key
106
-
107
- accept = Base64.encode64(Digest::SHA1.digest(sec_key + GUID)).strip
108
-
109
- upgrade = "HTTP/1.1 101 Switching Protocols\r\n"
110
- upgrade << "Upgrade: websocket\r\n"
111
- upgrade << "Connection: Upgrade\r\n"
112
- upgrade << "Sec-WebSocket-Accept: #{accept}\r\n"
113
- upgrade << "\r\n"
114
- upgrade
62
+
63
+ accept = Base64.encode64(Digest::SHA1.digest(sec_key + Handshake::GUID)).strip
64
+ protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
65
+ supported = @protocols
66
+ proto = nil
67
+
68
+ headers = [
69
+ "HTTP/1.1 101 Switching Protocols",
70
+ "Upgrade: websocket",
71
+ "Connection: Upgrade",
72
+ "Sec-WebSocket-Accept: #{accept}"
73
+ ]
74
+
75
+ if protos and supported
76
+ protos = protos.split(/\s*,\s*/) if String === protos
77
+ proto = protos.find { |p| supported.include?(p) }
78
+ if proto
79
+ @protocol = proto
80
+ headers << "Sec-WebSocket-Protocol: #{proto}"
81
+ end
82
+ end
83
+
84
+ (headers + ['','']).join("\r\n")
115
85
  end
116
-
86
+
117
87
  def create_handshake
118
- Handshake.new(@socket.uri)
88
+ Handshake.new(@socket.uri, @protocols)
119
89
  end
120
-
90
+
121
91
  def parse(data)
122
- data.each_byte do |byte|
92
+ @reader.put(data.bytes.to_a)
93
+ buffer = true
94
+ while buffer
123
95
  case @stage
124
- when 0 then parse_opcode(byte)
125
- when 1 then parse_length(byte)
126
- when 2 then parse_extended_length(byte)
127
- when 3 then parse_mask(byte)
128
- when 4 then parse_payload(byte)
96
+ when 0 then
97
+ buffer = @reader.read(1)
98
+ parse_opcode(buffer[0]) if buffer
99
+
100
+ when 1 then
101
+ buffer = @reader.read(1)
102
+ parse_length(buffer[0]) if buffer
103
+
104
+ when 2 then
105
+ buffer = @reader.read(@length_size)
106
+ parse_extended_length(buffer) if buffer
107
+
108
+ when 3 then
109
+ buffer = @reader.read(4)
110
+ if buffer
111
+ @mask = buffer
112
+ @stage = 4
113
+ end
114
+
115
+ when 4 then
116
+ buffer = @reader.read(@length)
117
+ if buffer
118
+ @payload = buffer
119
+ emit_frame
120
+ @stage = 0
121
+ end
129
122
  end
130
- emit_frame if @stage == 4 and @length == 0
131
123
  end
124
+
132
125
  nil
133
126
  end
134
-
127
+
135
128
  def frame(data, type = nil, code = nil)
136
129
  return nil if @closed
137
-
138
- type ||= (String === data ? :text : :binary)
139
- data = data.bytes.to_a if data.respond_to?(:bytes)
140
-
141
- if code
142
- data = [code].pack('n').bytes.to_a + data
130
+
131
+ is_text = (String === data)
132
+ opcode = OPCODES[type || (is_text ? :text : :binary)]
133
+ buffer = data.respond_to?(:bytes) ? data.bytes.to_a : data
134
+ insert = code ? 2 : 0
135
+ length = buffer.size + insert
136
+ header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10)
137
+ offset = header + (@masking ? 4 : 0)
138
+ masked = @masking ? MASK : 0
139
+ frame = Array.new(offset)
140
+
141
+ frame[0] = FIN | opcode
142
+
143
+ if length <= 125
144
+ frame[1] = masked | length
145
+ elsif length <= 65535
146
+ frame[1] = masked | 126
147
+ frame[2] = (length >> 8) & BYTE
148
+ frame[3] = length & BYTE
149
+ else
150
+ frame[1] = masked | 127
151
+ frame[2] = (length >> 56) & BYTE
152
+ frame[3] = (length >> 48) & BYTE
153
+ frame[4] = (length >> 40) & BYTE
154
+ frame[5] = (length >> 32) & BYTE
155
+ frame[6] = (length >> 24) & BYTE
156
+ frame[7] = (length >> 16) & BYTE
157
+ frame[8] = (length >> 8) & BYTE
158
+ frame[9] = length & BYTE
143
159
  end
144
-
145
- frame = (FIN | OPCODES[type]).chr
146
- length = data.size
147
- masked = @masking ? MASK : 0
148
-
149
- case length
150
- when 0..125 then
151
- frame << (masked | length).chr
152
- when 126..65535 then
153
- frame << (masked | 126).chr
154
- frame << [length].pack('n')
155
- else
156
- frame << (masked | 127).chr
157
- frame << [length >> 32, length & 0xFFFFFFFF].pack('NN')
160
+
161
+ if code
162
+ buffer = [(code >> 8) & BYTE, code & BYTE] + buffer
158
163
  end
159
-
164
+
160
165
  if @masking
161
- mask = (1..4).map { rand 256 }
162
- data.each_with_index do |byte, i|
163
- data[i] = byte ^ mask[i % 4]
164
- end
165
- frame << mask.pack('C*')
166
+ mask = [rand(256), rand(256), rand(256), rand(256)]
167
+ frame[header...offset] = mask
168
+ buffer = Mask.mask(buffer, mask)
166
169
  end
167
-
168
- WebSocket.encode(frame) + WebSocket.encode(data)
170
+
171
+ frame.concat(buffer)
172
+
173
+ WebSocket.encode(frame)
169
174
  end
170
-
175
+
171
176
  def close(code = nil, reason = nil, &callback)
172
177
  return if @closed
173
178
  @closing_callback ||= callback
174
179
  @socket.send(reason || '', :close, code || ERRORS[:normal_closure])
175
180
  @closed = true
176
181
  end
177
-
182
+
178
183
  private
179
-
184
+
180
185
  def parse_opcode(data)
181
186
  if [RSV1, RSV2, RSV3].any? { |rsv| (data & rsv) == rsv }
182
187
  return @socket.close(ERRORS[:protocol_error], nil, false)
183
188
  end
184
-
189
+
185
190
  @final = (data & FIN) == FIN
186
191
  @opcode = (data & OPCODE)
187
192
  @mask = []
188
193
  @payload = []
189
-
194
+
190
195
  unless OPCODES.values.include?(@opcode)
191
196
  return @socket.close(ERRORS[:protocol_error], nil, false)
192
197
  end
193
-
198
+
194
199
  unless FRAGMENTED_OPCODES.include?(@opcode) or @final
195
200
  return @socket.close(ERRORS[:protocol_error], nil, false)
196
201
  end
197
-
202
+
198
203
  if @mode and OPENING_OPCODES.include?(@opcode)
199
204
  return @socket.close(ERRORS[:protocol_error], nil, false)
200
205
  end
201
-
206
+
202
207
  @stage = 1
203
208
  end
204
-
209
+
205
210
  def parse_length(data)
206
211
  @masked = (data & MASK) == MASK
207
212
  @length = (data & LENGTH)
208
-
213
+
209
214
  if @length <= 125
210
215
  @stage = @masked ? 3 : 4
211
216
  else
212
- @length_buffer = []
213
- @length_size = (@length == 126) ? 2 : 8
214
- @stage = 2
217
+ @length_size = (@length == 126) ? 2 : 8
218
+ @stage = 2
215
219
  end
216
220
  end
217
-
218
- def parse_extended_length(data)
219
- @length_buffer << data
220
- return unless @length_buffer.size == @length_size
221
- @length = integer(@length_buffer)
221
+
222
+ def parse_extended_length(buffer)
223
+ @length = integer(buffer)
222
224
  @stage = @masked ? 3 : 4
223
225
  end
224
-
225
- def parse_mask(data)
226
- @mask << data
227
- return if @mask.size < 4
228
- @stage = 4
229
- end
230
-
231
- def parse_payload(data)
232
- @payload << data
233
- return if @payload.size < @length
234
- emit_frame
235
- end
236
-
226
+
237
227
  def emit_frame
238
- payload = unmask(@payload, @mask)
239
-
228
+ payload = @masked ? Mask.mask(@payload, @mask) : @payload
229
+
240
230
  case @opcode
241
231
  when OPCODES[:continuation] then
242
232
  return @socket.close(ERRORS[:protocol_error], nil, false) unless @mode
243
- @buffer += payload
233
+ @buffer.concat(payload)
244
234
  if @final
245
235
  message = @buffer
246
236
  message = WebSocket.encode(message, true) if @mode == :text
@@ -262,7 +252,7 @@ module Faye
262
252
  end
263
253
  else
264
254
  @mode = :text
265
- @buffer += payload
255
+ @buffer.concat(payload)
266
256
  end
267
257
 
268
258
  when OPCODES[:binary] then
@@ -270,22 +260,22 @@ module Faye
270
260
  @socket.receive(payload)
271
261
  else
272
262
  @mode = :binary
273
- @buffer += payload
263
+ @buffer.concat(payload)
274
264
  end
275
265
 
276
266
  when OPCODES[:close] then
277
267
  code = (payload.size >= 2) ? 256 * payload[0] + payload[1] : nil
278
-
268
+
279
269
  unless (payload.size == 0) or
280
270
  (code && code >= 3000 && code < 5000) or
281
271
  ERROR_CODES.include?(code)
282
272
  code = ERRORS[:protocol_error]
283
273
  end
284
-
274
+
285
275
  if payload.size > 125 or not WebSocket.valid_utf8?(payload[2..-1] || [])
286
276
  code = ERRORS[:protocol_error]
287
277
  end
288
-
278
+
289
279
  reason = (payload.size > 2) ? WebSocket.encode(payload[2..-1], true) : nil
290
280
  @socket.close(code, reason, false)
291
281
  @closing_callback.call if @closing_callback
@@ -294,14 +284,13 @@ module Faye
294
284
  return @socket.close(ERRORS[:protocol_error], nil, false) if payload.size > 125
295
285
  @socket.send(payload, :pong)
296
286
  end
297
- @stage = 0
298
287
  end
299
288
 
300
289
  def reset
301
290
  @buffer = []
302
291
  @mode = nil
303
292
  end
304
-
293
+
305
294
  def integer(bytes)
306
295
  number = 0
307
296
  bytes.each_with_index do |data, i|
@@ -309,17 +298,7 @@ module Faye
309
298
  end
310
299
  number
311
300
  end
312
-
313
- def unmask(payload, mask)
314
- unmasked = []
315
- payload.each_with_index do |byte, i|
316
- byte = byte ^ mask[i % 4] if mask.size > 0
317
- unmasked << byte
318
- end
319
- unmasked
320
- end
321
301
  end
322
-
302
+
323
303
  end
324
304
  end
325
-