faye-websocket 0.1.2 → 0.2.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.

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
-