Add closing handshake timeout.
If either the Close message or socket close fails to arrive within 4
minutes, we should close the connection outselves.
Also add tests for the closing handshake timeout (using a 1ms timeout).
BUG=230756
TEST=net_unittests --gtest_filter=WebSocketChannel*
Review URL: https://codereview.chromium.org/31813007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@230367 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc
index e5848608..6bc2a54 100644
--- a/net/websockets/websocket_channel_test.cc
+++ b/net/websockets/websocket_channel_test.cc
@@ -20,6 +20,7 @@
#include "base/safe_numerics.h"
#include "base/strings/string_piece.h"
#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
#include "net/url_request/url_request_context.h"
#include "net/websockets/websocket_errors.h"
#include "net/websockets/websocket_event_interface.h"
@@ -84,6 +85,8 @@
namespace {
+using ::base::TimeDelta;
+
using ::testing::AnyNumber;
using ::testing::InSequence;
using ::testing::MockFunction;
@@ -116,6 +119,10 @@
// kDefaultSendQuotaLowWaterMark change.
const size_t kDefaultQuotaRefreshTrigger = (1 << 16) + 1;
+// TestTimeouts::tiny_timeout() is 100ms! I could run halfway around the world
+// in that time! I would like my tests to run a bit quicker.
+const int kVeryTinyTimeoutMillis = 1;
+
// This mock is for testing expectations about how the EventInterface is used.
class MockWebSocketEventInterface : public WebSocketEventInterface {
public:
@@ -353,6 +360,21 @@
return ::testing::MakeMatcher(new EqualsFramesMatcher<N>(&frames));
}
+// TestClosure works like TestCompletionCallback, but doesn't take an argument.
+class TestClosure {
+ public:
+ base::Closure closure() { return base::Bind(callback_.callback(), OK); }
+
+ void WaitForResult() { callback_.WaitForResult(); }
+
+ private:
+ // Delegate to TestCompletionCallback for the implementation.
+ TestCompletionCallback callback_;
+};
+
+// A GoogleMock action to run a Closure.
+ACTION_P(InvokeClosure, closure) { closure.Run(); }
+
// A FakeWebSocketStream whose ReadFrames() function returns data.
class ReadableFakeWebSocketStream : public FakeWebSocketStream {
public:
@@ -1366,6 +1388,69 @@
base::MessageLoop::current()->RunUntilIdle();
}
+// The closing handshake times out and sends an OnDropChannel event if no
+// response to the client Close message is received.
+TEST_F(WebSocketChannelEventInterfaceTest,
+ ClientInitiatedClosingHandshakeTimesOut) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_IO_PENDING);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ // This checkpoint object verifies that the OnDropChannel message comes after
+ // the timeout.
+ MockFunction<void(int)> checkpoint;
+ TestClosure completion;
+ {
+ InSequence s;
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _))
+ .WillOnce(InvokeClosure(completion.closure()));
+ }
+ CreateChannelAndConnectSuccessfully();
+ // OneShotTimer is not very friendly to testing; there is no apparent way to
+ // set an expectation on it. Instead the tests need to infer that the timeout
+ // was fired by the behaviour of the WebSocketChannel object.
+ channel_->SetClosingHandshakeTimeoutForTesting(
+ TimeDelta::FromMilliseconds(kVeryTinyTimeoutMillis));
+ channel_->StartClosingHandshake(kWebSocketNormalClosure, "");
+ checkpoint.Call(1);
+ completion.WaitForResult();
+}
+
+// The closing handshake times out and sends an OnDropChannel event if a Close
+// message is received but the connection isn't closed by the remote host.
+TEST_F(WebSocketChannelEventInterfaceTest,
+ ServerInitiatedClosingHandshakeTimesOut) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ MockFunction<void(int)> checkpoint;
+ TestClosure completion;
+ {
+ InSequence s;
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_, OnClosingHandshake());
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _))
+ .WillOnce(InvokeClosure(completion.closure()));
+ }
+ CreateChannelAndConnectSuccessfully();
+ channel_->SetClosingHandshakeTimeoutForTesting(
+ TimeDelta::FromMilliseconds(kVeryTinyTimeoutMillis));
+ checkpoint.Call(1);
+ completion.WaitForResult();
+}
+
// RFC6455 5.1 "a client MUST mask all frames that it sends to the server".
// WebSocketChannel actually only sets the mask bit in the header, it doesn't
// perform masking itself (not all transports actually use masking).
@@ -1733,5 +1818,122 @@
CreateChannelAndConnectSuccessfully();
}
+// Set the closing handshake timeout to a very tiny value before connecting.
+class WebSocketChannelStreamTimeoutTest : public WebSocketChannelStreamTest {
+ protected:
+ WebSocketChannelStreamTimeoutTest() {}
+
+ virtual void CreateChannelAndConnectSuccessfully() OVERRIDE {
+ set_stream(mock_stream_.Pass());
+ CreateChannelAndConnect();
+ channel_->SetClosingHandshakeTimeoutForTesting(
+ TimeDelta::FromMilliseconds(kVeryTinyTimeoutMillis));
+ connect_data_.factory.connect_delegate->OnSuccess(stream_.Pass());
+ }
+};
+
+// In this case the server initiates the closing handshake with a Close
+// message. WebSocketChannel responds with a matching Close message, and waits
+// for the server to close the TCP/IP connection. The server never closes the
+// connection, so the closing handshake times out and WebSocketChannel closes
+// the connection itself.
+TEST_F(WebSocketChannelStreamTimeoutTest, ServerInitiatedCloseTimesOut) {
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnFrames(&frames))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ MockFunction<void(int)> checkpoint;
+ TestClosure completion;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*mock_stream_, Close())
+ .WillOnce(InvokeClosure(completion.closure()));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ completion.WaitForResult();
+}
+
+// In this case the client initiates the closing handshake by sending a Close
+// message. WebSocketChannel waits for a Close message in response from the
+// server. The server never responds to the Close message, so the closing
+// handshake times out and WebSocketChannel closes the connection.
+TEST_F(WebSocketChannelStreamTimeoutTest, ClientInitiatedCloseTimesOut) {
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ TestClosure completion;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, Close())
+ .WillOnce(InvokeClosure(completion.closure()));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->StartClosingHandshake(kWebSocketNormalClosure, "OK");
+ completion.WaitForResult();
+}
+
+// In this case the client initiates the closing handshake and the server
+// responds with a matching Close message. WebSocketChannel waits for the server
+// to close the TCP/IP connection, but it never does. The closing handshake
+// times out and WebSocketChannel closes the connection.
+TEST_F(WebSocketChannelStreamTimeoutTest, ConnectionCloseTimesOut) {
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}};
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ TestClosure completion;
+ ScopedVector<WebSocketFrame>* read_frames = NULL;
+ CompletionCallback read_callback;
+ {
+ InSequence s;
+ // Copy the arguments to ReadFrames so that the test can call the callback
+ // after it has send the close message.
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&read_frames),
+ SaveArg<1>(&read_callback),
+ Return(ERR_IO_PENDING)));
+ // The first real event that happens is the client sending the Close
+ // message.
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
+ .WillOnce(Return(OK));
+ // The |read_frames| callback is called (from this test case) at this
+ // point. ReadFrames is called again by WebSocketChannel, waiting for
+ // ERR_CONNECTION_CLOSED.
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ // The timeout happens and so WebSocketChannel closes the stream.
+ EXPECT_CALL(*mock_stream_, Close())
+ .WillOnce(InvokeClosure(completion.closure()));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->StartClosingHandshake(kWebSocketNormalClosure, "OK");
+ ASSERT_TRUE(read_frames);
+ // Provide the "Close" message from the server.
+ *read_frames = CreateFrameVector(frames);
+ read_callback.Run(OK);
+ completion.WaitForResult();
+}
+
} // namespace
} // namespace net