_bushido-faye-websocket 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGELOG.txt +56 -0
  2. data/README.rdoc +366 -0
  3. data/examples/app.rb +50 -0
  4. data/examples/autobahn_client.rb +44 -0
  5. data/examples/client.rb +30 -0
  6. data/examples/config.ru +17 -0
  7. data/examples/haproxy.conf +21 -0
  8. data/examples/server.rb +44 -0
  9. data/examples/sse.html +39 -0
  10. data/examples/ws.html +44 -0
  11. data/ext/faye_websocket_mask/FayeWebsocketMaskService.java +61 -0
  12. data/ext/faye_websocket_mask/extconf.rb +5 -0
  13. data/ext/faye_websocket_mask/faye_websocket_mask.c +33 -0
  14. data/lib/faye/adapters/goliath.rb +47 -0
  15. data/lib/faye/adapters/rainbows.rb +32 -0
  16. data/lib/faye/adapters/rainbows_client.rb +70 -0
  17. data/lib/faye/adapters/thin.rb +62 -0
  18. data/lib/faye/eventsource.rb +124 -0
  19. data/lib/faye/websocket.rb +216 -0
  20. data/lib/faye/websocket/adapter.rb +21 -0
  21. data/lib/faye/websocket/api.rb +96 -0
  22. data/lib/faye/websocket/api/event.rb +33 -0
  23. data/lib/faye/websocket/api/event_target.rb +34 -0
  24. data/lib/faye/websocket/client.rb +84 -0
  25. data/lib/faye/websocket/draft75_parser.rb +87 -0
  26. data/lib/faye/websocket/draft76_parser.rb +84 -0
  27. data/lib/faye/websocket/hybi_parser.rb +320 -0
  28. data/lib/faye/websocket/hybi_parser/handshake.rb +78 -0
  29. data/lib/faye/websocket/hybi_parser/stream_reader.rb +29 -0
  30. data/lib/faye/websocket/utf8_match.rb +8 -0
  31. data/spec/faye/websocket/client_spec.rb +179 -0
  32. data/spec/faye/websocket/draft75_parser_examples.rb +48 -0
  33. data/spec/faye/websocket/draft75_parser_spec.rb +27 -0
  34. data/spec/faye/websocket/draft76_parser_spec.rb +34 -0
  35. data/spec/faye/websocket/hybi_parser_spec.rb +156 -0
  36. data/spec/rainbows.conf +3 -0
  37. data/spec/server.crt +15 -0
  38. data/spec/server.key +15 -0
  39. data/spec/spec_helper.rb +68 -0
  40. metadata +158 -0
@@ -0,0 +1,78 @@
1
+ module Faye
2
+ class WebSocket
3
+ class HybiParser
4
+
5
+ class Handshake
6
+ GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
7
+
8
+ attr_reader :protocol
9
+
10
+ def initialize(uri, protocols)
11
+ @uri = uri
12
+ @protocols = protocols
13
+ @key = Base64.encode64((1..16).map { rand(255).chr } * '').strip
14
+ @accept = Base64.encode64(Digest::SHA1.digest(@key + GUID)).strip
15
+ @buffer = []
16
+ end
17
+
18
+ def request_data
19
+ hostname = @uri.host + (@uri.port ? ":#{@uri.port}" : '')
20
+
21
+ headers = [
22
+ "GET #{@uri.path}#{@uri.query ? '?' : ''}#{@uri.query} HTTP/1.1",
23
+ "Host: #{hostname}",
24
+ "Upgrade: websocket",
25
+ "Connection: Upgrade",
26
+ "Sec-WebSocket-Key: #{@key}",
27
+ "Sec-WebSocket-Version: 13"
28
+ ]
29
+
30
+ if @protocols
31
+ headers << "Sec-WebSocket-Protocol: #{@protocols * ', '}"
32
+ end
33
+
34
+ (headers + ['','']).join("\r\n")
35
+ end
36
+
37
+ def parse(data)
38
+ message = []
39
+ complete = false
40
+ data.each_byte do |byte|
41
+ if complete
42
+ message << byte
43
+ else
44
+ @buffer << byte
45
+ complete ||= complete?
46
+ end
47
+ end
48
+ message
49
+ end
50
+
51
+ def complete?
52
+ @buffer[-4..-1] == [0x0D, 0x0A, 0x0D, 0x0A]
53
+ end
54
+
55
+ def valid?
56
+ data = WebSocket.encode(@buffer)
57
+
58
+ response = Net::HTTPResponse.read_new(Net::BufferedIO.new(StringIO.new(data)))
59
+ return false unless response.code.to_i == 101
60
+
61
+ upgrade = response['Upgrade']
62
+ connection = response['Connection']
63
+ protocol = response['Sec-WebSocket-Protocol']
64
+
65
+ @protocol = @protocols && @protocols.include?(protocol) ?
66
+ protocol :
67
+ nil
68
+
69
+ upgrade and upgrade =~ /^websocket$/i and
70
+ connection and connection.split(/\s*,\s*/).include?('Upgrade') and
71
+ ((!@protocols and !protocol) or @protocol) and
72
+ response['Sec-WebSocket-Accept'] == @accept
73
+ end
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,29 @@
1
+ module Faye
2
+ class WebSocket
3
+ class HybiParser
4
+
5
+ class StreamReader
6
+ def initialize
7
+ @queue = []
8
+ end
9
+
10
+ def read(length)
11
+ read_bytes(length)
12
+ end
13
+
14
+ def put(bytes)
15
+ return unless bytes and bytes.size > 0
16
+ @queue.concat(bytes)
17
+ end
18
+
19
+ private
20
+
21
+ def read_bytes(length)
22
+ return nil if length > @queue.size
23
+ @queue.shift(length)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ module Faye
2
+ class WebSocket
3
+
4
+ # http://www.w3.org/International/questions/qa-forms-utf-8.en.php
5
+ 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})*$/
6
+
7
+ end
8
+ end
@@ -0,0 +1,179 @@
1
+ # encoding=utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ WebSocketSteps = EM::RSpec.async_steps do
6
+ def server(port, backend, secure, &callback)
7
+ @server = EchoServer.new
8
+ @server.listen(port, backend, secure)
9
+ @port = port
10
+ EM.add_timer(0.1, &callback)
11
+ end
12
+
13
+ def stop(&callback)
14
+ @server.stop
15
+ EM.next_tick(&callback)
16
+ end
17
+
18
+ def open_socket(url, protocols, &callback)
19
+ done = false
20
+
21
+ resume = lambda do |open|
22
+ unless done
23
+ done = true
24
+ @open = open
25
+ callback.call
26
+ end
27
+ end
28
+
29
+ @ws = Faye::WebSocket::Client.new(url, protocols)
30
+
31
+ @ws.onopen = lambda { |e| resume.call(true) }
32
+ @ws.onclose = lambda { |e| resume.call(false) }
33
+ end
34
+
35
+ def close_socket(&callback)
36
+ @ws.onclose = lambda do |e|
37
+ @open = false
38
+ callback.call
39
+ end
40
+ @ws.close
41
+ end
42
+
43
+ def check_open(&callback)
44
+ @open.should == true
45
+ callback.call
46
+ end
47
+
48
+ def check_closed(&callback)
49
+ @open.should == false
50
+ callback.call
51
+ end
52
+
53
+ def check_protocol(protocol, &callback)
54
+ @ws.protocol.should == protocol
55
+ callback.call
56
+ end
57
+
58
+ def listen_for_message(&callback)
59
+ @ws.add_event_listener('message', lambda { |e| @message = e.data })
60
+ callback.call
61
+ end
62
+
63
+ def send_message(message, &callback)
64
+ @ws.send(message)
65
+ EM.add_timer(0.1, &callback)
66
+ end
67
+
68
+ def check_response(message, &callback)
69
+ @message.should == message
70
+ callback.call
71
+ end
72
+
73
+ def check_no_response(&callback)
74
+ @message.should == nil
75
+ callback.call
76
+ end
77
+ end
78
+
79
+ describe Faye::WebSocket::Client do
80
+ next if Faye::WebSocket.jruby?
81
+ include WebSocketSteps
82
+
83
+ let(:protocols) { ["foo", "echo"] }
84
+ let(:plain_text_url) { "ws://0.0.0.0:8000/" }
85
+ let(:secure_url) { "wss://0.0.0.0:8000/" }
86
+
87
+ before do
88
+ Thread.new { EM.run }
89
+ sleep(0.1) until EM.reactor_running?
90
+ end
91
+
92
+ shared_examples_for "socket client" do
93
+ it "can open a connection" do
94
+ open_socket(socket_url, protocols)
95
+ check_open
96
+ check_protocol("echo")
97
+ end
98
+
99
+ it "cannot open a connection to the wrong host" do
100
+ open_socket(blocked_url, protocols)
101
+ check_closed
102
+ end
103
+
104
+ it "cannot open a connection with unacceptable protocols" do
105
+ open_socket(socket_url, ["foo"])
106
+ check_closed
107
+ end
108
+
109
+ it "can close the connection" do
110
+ open_socket(socket_url, protocols)
111
+ close_socket
112
+ check_closed
113
+ end
114
+
115
+ describe "in the OPEN state" do
116
+ before { open_socket(socket_url, protocols) }
117
+
118
+ it "can send and receive messages" do
119
+ listen_for_message
120
+ send_message "I expect this to be echoed"
121
+ check_response "I expect this to be echoed"
122
+ end
123
+
124
+ it "sends numbers as strings" do
125
+ listen_for_message
126
+ send_message 13
127
+ check_response "13"
128
+ end
129
+ end
130
+
131
+ describe "in the CLOSED state" do
132
+ before do
133
+ open_socket(socket_url, protocols)
134
+ close_socket
135
+ end
136
+
137
+ it "cannot send and receive messages" do
138
+ listen_for_message
139
+ send_message "I expect this to be echoed"
140
+ check_no_response
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "with a plain-text Thin server" do
146
+ let(:socket_url) { plain_text_url }
147
+ let(:blocked_url) { secure_url }
148
+
149
+ before { server 8000, :thin, false }
150
+ after { sync ; stop }
151
+
152
+ it_should_behave_like "socket client"
153
+ end
154
+
155
+ describe "with a plain-text Rainbows server" do
156
+ next if Faye::WebSocket.rbx?
157
+
158
+ let(:socket_url) { plain_text_url }
159
+ let(:blocked_url) { secure_url }
160
+
161
+ before { server 8000, :rainbows, false }
162
+ after { sync ; stop }
163
+
164
+ it_should_behave_like "socket client"
165
+ end
166
+
167
+ describe "with a secure Thin server" do
168
+ next if Faye::WebSocket.rbx?
169
+
170
+ let(:socket_url) { secure_url }
171
+ let(:blocked_url) { plain_text_url }
172
+
173
+ before { server 8000, :thin, true }
174
+ after { sync ; stop }
175
+
176
+ it_should_behave_like "socket client"
177
+ end
178
+ end
179
+
@@ -0,0 +1,48 @@
1
+ # encoding=utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "draft-75 parser" do
6
+ it "parses text frames" do
7
+ @web_socket.should_receive(:receive).with("Hello")
8
+ parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
9
+ end
10
+
11
+ it "parses multiple frames from the same packet" do
12
+ @web_socket.should_receive(:receive).with("Hello").exactly(2)
13
+ parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
14
+ end
15
+
16
+ it "parses text frames beginning 0x00-0x7F" do
17
+ @web_socket.should_receive(:receive).with("Hello")
18
+ parse [0x66, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
19
+ end
20
+
21
+ it "ignores frames with a length header" do
22
+ @web_socket.should_not_receive(:receive)
23
+ parse [0x80, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
24
+ end
25
+
26
+ it "parses text following an ignored block" do
27
+ @web_socket.should_receive(:receive).with("Hello")
28
+ parse [0x80, 0x02, 0x48, 0x65, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
29
+ end
30
+
31
+ it "parses multibyte text frames" do
32
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
33
+ parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
34
+ end
35
+
36
+ it "parses frames received in several packets" do
37
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
38
+ parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65]
39
+ parse [0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
40
+ end
41
+
42
+ it "parses fragmented frames" do
43
+ @web_socket.should_receive(:receive).with("Hello")
44
+ parse [0x00, 0x48, 0x65, 0x6c]
45
+ parse [0x6c, 0x6f, 0xff]
46
+ end
47
+ end
48
+
@@ -0,0 +1,27 @@
1
+ # encoding=utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Faye::WebSocket::Draft75Parser do
6
+ include EncodingHelper
7
+
8
+ before do
9
+ @web_socket = mock Faye::WebSocket
10
+ @parser = Faye::WebSocket::Draft75Parser.new(@web_socket)
11
+ end
12
+
13
+ describe :parse do
14
+ it_should_behave_like "draft-75 parser"
15
+ end
16
+
17
+ describe :frame do
18
+ it "returns the given string formatted as a WebSocket frame" do
19
+ bytes(@parser.frame "Hello").should == [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
20
+ end
21
+
22
+ it "encodes multibyte characters correctly" do
23
+ message = encode "Apple = "
24
+ bytes(@parser.frame message).should == [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,34 @@
1
+ # encoding=utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Faye::WebSocket::Draft76Parser do
6
+ include EncodingHelper
7
+
8
+ before do
9
+ @web_socket = mock Faye::WebSocket
10
+ @parser = Faye::WebSocket::Draft76Parser.new(@web_socket)
11
+ @parser.instance_eval { @handshake_complete = true }
12
+ end
13
+
14
+ describe :parse do
15
+ it_should_behave_like "draft-75 parser"
16
+
17
+ it "closes the socket if a close frame is received" do
18
+ @web_socket.should_receive(:close)
19
+ parse [0xFF, 0x00]
20
+ end
21
+ end
22
+
23
+ describe :frame do
24
+ it "returns the given string formatted as a WebSocket frame" do
25
+ bytes(@parser.frame "Hello").should == [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
26
+ end
27
+
28
+ it "encodes multibyte characters correctly" do
29
+ message = encode "Apple = "
30
+ bytes(@parser.frame message).should == [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,156 @@
1
+ # encoding=utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Faye::WebSocket::HybiParser do
6
+ include EncodingHelper
7
+
8
+ before do
9
+ @web_socket = mock Faye::WebSocket
10
+ @parser = Faye::WebSocket::HybiParser.new(@web_socket)
11
+ end
12
+
13
+ describe :parse do
14
+ let(:mask) { (1..4).map { rand 255 } }
15
+
16
+ def mask_message(*bytes)
17
+ output = []
18
+ bytes.each_with_index do |byte, i|
19
+ output[i] = byte ^ mask[i % 4]
20
+ end
21
+ output
22
+ end
23
+
24
+ it "parses unmasked text frames" do
25
+ @web_socket.should_receive(:receive).with("Hello")
26
+ parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
27
+ end
28
+
29
+ it "parses multiple frames from the same packet" do
30
+ @web_socket.should_receive(:receive).with("Hello").exactly(2)
31
+ parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
32
+ end
33
+
34
+ it "parses empty text frames" do
35
+ @web_socket.should_receive(:receive).with("")
36
+ parse [0x81, 0x00]
37
+ end
38
+
39
+ it "parses fragmented text frames" do
40
+ @web_socket.should_receive(:receive).with("Hello")
41
+ parse [0x01, 0x03, 0x48, 0x65, 0x6c]
42
+ parse [0x80, 0x02, 0x6c, 0x6f]
43
+ end
44
+
45
+ it "parses masked text frames" do
46
+ @web_socket.should_receive(:receive).with("Hello")
47
+ parse [0x81, 0x85] + mask + mask_message(0x48, 0x65, 0x6c, 0x6c, 0x6f)
48
+ end
49
+
50
+ it "parses masked empty text frames" do
51
+ @web_socket.should_receive(:receive).with("")
52
+ parse [0x81, 0x80] + mask + mask_message()
53
+ end
54
+
55
+ it "parses masked fragmented text frames" do
56
+ @web_socket.should_receive(:receive).with("Hello")
57
+ parse [0x01, 0x81] + mask + mask_message(0x48)
58
+ parse [0x80, 0x84] + mask + mask_message(0x65, 0x6c, 0x6c, 0x6f)
59
+ end
60
+
61
+ it "closes the socket if the frame has an unrecognized opcode" do
62
+ @web_socket.should_receive(:close).with(1002, nil, false)
63
+ parse [0x83, 0x00]
64
+ end
65
+
66
+ it "closes the socket if a close frame is received" do
67
+ @web_socket.should_receive(:close).with(1000, "Hello", false)
68
+ parse [0x88, 0x07, 0x03, 0xe8, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
69
+ end
70
+
71
+ it "parses unmasked multibyte text frames" do
72
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
73
+ parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
74
+ end
75
+
76
+ it "parses frames received in several packets" do
77
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
78
+ parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c]
79
+ parse [0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
80
+ end
81
+
82
+ it "parses fragmented multibyte text frames" do
83
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
84
+ parse [0x01, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3]
85
+ parse [0x80, 0x01, 0xbf]
86
+ end
87
+
88
+ it "parses masked multibyte text frames" do
89
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
90
+ parse [0x81, 0x8b] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf)
91
+ end
92
+
93
+ it "parses masked fragmented multibyte text frames" do
94
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
95
+ parse [0x01, 0x8a] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3)
96
+ parse [0x80, 0x81] + mask + mask_message(0xbf)
97
+ end
98
+
99
+ it "parses unmasked medium-length text frames" do
100
+ @web_socket.should_receive(:receive).with("Hello" * 40)
101
+ parse [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
102
+ end
103
+
104
+ it "parses masked medium-length text frames" do
105
+ @web_socket.should_receive(:receive).with("Hello" * 40)
106
+ parse [0x81, 0xfe, 0x00, 0xc8] + mask + mask_message(*([0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40))
107
+ end
108
+
109
+ it "replies to pings with a pong" do
110
+ @web_socket.should_receive(:send).with([0x4f, 0x48, 0x41, 0x49], :pong)
111
+ parse [0x89, 0x04, 0x4f, 0x48, 0x41, 0x49]
112
+ end
113
+ end
114
+
115
+ describe :frame do
116
+ it "returns the given string formatted as a WebSocket frame" do
117
+ bytes(@parser.frame "Hello").should == [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
118
+ end
119
+
120
+ it "encodes multibyte characters correctly" do
121
+ message = encode "Apple = "
122
+ bytes(@parser.frame message).should == [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
123
+ end
124
+
125
+ it "encodes medium-length strings using extra length bytes" do
126
+ message = "Hello" * 40
127
+ bytes(@parser.frame message).should == [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
128
+ end
129
+
130
+ it "encodes long strings using extra length bytes" do
131
+ message = "Hello" * 13108
132
+ bytes(@parser.frame message).should == [0x81, 0x7f] +
133
+ [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04] +
134
+ [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 13108
135
+ end
136
+
137
+ it "encodes close frames with an error code" do
138
+ frame = @parser.frame "Hello", :close, 1002
139
+ bytes(frame).should == [0x88, 0x07, 0x03, 0xea, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
140
+ end
141
+
142
+ it "encodes pong frames" do
143
+ bytes(@parser.frame '', :pong).should == [0x8a, 0x00]
144
+ end
145
+ end
146
+
147
+ describe :utf8 do
148
+ it "detects valid UTF-8" do
149
+ Faye::WebSocket.valid_utf8?( [72, 101, 108, 108, 111, 45, 194, 181, 64, 195, 159, 195, 182, 195, 164, 195, 188, 195, 160, 195, 161, 45, 85, 84, 70, 45, 56, 33, 33] ).should be_true
150
+ end
151
+
152
+ it "detects invalid UTF-8" do
153
+ Faye::WebSocket.valid_utf8?( [206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181, 237, 160, 128, 101, 100, 105, 116, 101, 100] ).should be_false
154
+ end
155
+ end
156
+ end