summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authoryhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-11 06:04:11 +0000
committeryhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-11 06:04:11 +0000
commitce9f7ffd61434ece2521087e1aeb68d9635a6700 (patch)
tree24c798902a7a2816a56798717c67814ab3d6afa8 /net
parenta4b7df33bda0468e476389d38230669e9f6169e6 (diff)
downloadchromium_src-ce9f7ffd61434ece2521087e1aeb68d9635a6700.zip
chromium_src-ce9f7ffd61434ece2521087e1aeb68d9635a6700.tar.gz
chromium_src-ce9f7ffd61434ece2521087e1aeb68d9635a6700.tar.bz2
Introduce WebSocketDeflateStream.
This CL introduces WebSocketDeflateStream, a WebSocketStream subclass for permessage-deflate WebSocket extension[1]. Currently the implementation compresses all outgoing data frames: it should be fixed in the future. [1] http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-12 BUG=280910 Review URL: https://codereview.chromium.org/26202002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@228129 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/net.gyp7
-rw-r--r--net/websockets/README7
-rw-r--r--net/websockets/websocket_deflate_stream.cc290
-rw-r--r--net/websockets/websocket_deflate_stream.h90
-rw-r--r--net/websockets/websocket_deflate_stream_test.cc934
-rw-r--r--net/websockets/websocket_inflater_test.cc23
-rw-r--r--net/websockets/websocket_test_util.cc28
-rw-r--r--net/websockets/websocket_test_util.h23
8 files changed, 1378 insertions, 24 deletions
diff --git a/net/net.gyp b/net/net.gyp
index 1fbff15..5959c33 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -1109,8 +1109,10 @@
'websockets/websocket_basic_stream.h',
'websockets/websocket_channel.cc',
'websockets/websocket_channel.h',
- 'websockets/websocket_deflater.h',
+ 'websockets/websocket_deflate_stream.cc',
+ 'websockets/websocket_deflate_stream.h',
'websockets/websocket_deflater.cc',
+ 'websockets/websocket_deflater.h',
'websockets/websocket_errors.cc',
'websockets/websocket_errors.h',
'websockets/websocket_extension.cc',
@@ -1891,6 +1893,7 @@
'url_request/view_cache_helper_unittest.cc',
'websockets/websocket_basic_stream_test.cc',
'websockets/websocket_channel_test.cc',
+ 'websockets/websocket_deflate_stream_test.cc',
'websockets/websocket_deflater_test.cc',
'websockets/websocket_errors_test.cc',
'websockets/websocket_extension_parser_test.cc',
@@ -1901,6 +1904,8 @@
'websockets/websocket_inflater_test.cc',
'websockets/websocket_job_test.cc',
'websockets/websocket_net_log_params_test.cc',
+ 'websockets/websocket_test_util.cc',
+ 'websockets/websocket_test_util.h',
'websockets/websocket_throttle_test.cc',
],
'conditions': [
diff --git a/net/websockets/README b/net/websockets/README
index e9fd214f..bca38c9 100644
--- a/net/websockets/README
+++ b/net/websockets/README
@@ -35,8 +35,11 @@ websocket_basic_stream_test.cc
websocket_channel.cc
websocket_channel.h
websocket_channel_test.cc
-websocket_deflater.h
+websocket_deflate_stream.cc
+websocket_deflate_stream.h
+websocket_deflate_stream_test.cc
websocket_deflater.cc
+websocket_deflater.h
websocket_deflater_test.cc
websocket_errors.cc
websocket_errors.h
@@ -60,6 +63,8 @@ websocket_mux.h
websocket_stream_base.h
websocket_stream.cc
websocket_stream.h
+websocket_test_util.cc
+websocket_test_util.h
These files are shared between the old and new implementations.
diff --git a/net/websockets/websocket_deflate_stream.cc b/net/websockets/websocket_deflate_stream.cc
new file mode 100644
index 0000000..d4fe775
--- /dev/null
+++ b/net/websockets/websocket_deflate_stream.cc
@@ -0,0 +1,290 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/websockets/websocket_deflate_stream.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/websockets/websocket_deflater.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_frame.h"
+#include "net/websockets/websocket_inflater.h"
+#include "net/websockets/websocket_stream.h"
+
+class GURL;
+
+namespace net {
+
+namespace {
+
+const int kWindowBits = 15;
+const size_t kChunkSize = 4 * 1024;
+
+} // namespace
+
+WebSocketDeflateStream::WebSocketDeflateStream(
+ scoped_ptr<WebSocketStream> stream)
+ : stream_(stream.Pass()),
+ deflater_(WebSocketDeflater::TAKE_OVER_CONTEXT),
+ inflater_(kChunkSize, kChunkSize),
+ reading_state_(NOT_READING),
+ writing_state_(NOT_WRITING),
+ current_reading_opcode_(WebSocketFrameHeader::kOpCodeText),
+ current_writing_opcode_(WebSocketFrameHeader::kOpCodeText) {
+ DCHECK(stream_);
+ deflater_.Initialize(kWindowBits);
+ inflater_.Initialize(kWindowBits);
+}
+
+WebSocketDeflateStream::~WebSocketDeflateStream() {}
+
+int WebSocketDeflateStream::ReadFrames(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback) {
+ CompletionCallback callback_to_pass =
+ base::Bind(&WebSocketDeflateStream::OnReadComplete,
+ base::Unretained(this),
+ base::Unretained(frames),
+ callback);
+ int result = stream_->ReadFrames(frames, callback_to_pass);
+ if (result < 0)
+ return result;
+ DCHECK_EQ(OK, result);
+ return InflateAndReadIfNecessary(frames, callback_to_pass);
+}
+
+int WebSocketDeflateStream::WriteFrames(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback) {
+ int result = Deflate(frames);
+ if (result != OK)
+ return result;
+ if (frames->empty())
+ return OK;
+ return stream_->WriteFrames(frames, callback);
+}
+
+void WebSocketDeflateStream::Close() {
+ stream_->Close();
+}
+
+std::string WebSocketDeflateStream::GetSubProtocol() const {
+ return stream_->GetSubProtocol();
+}
+
+std::string WebSocketDeflateStream::GetExtensions() const {
+ return stream_->GetExtensions();
+}
+
+int WebSocketDeflateStream::SendHandshakeRequest(
+ const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback) {
+ // TODO(yhirano) handshake related functions will be moved to somewhere.
+ NOTIMPLEMENTED();
+ return OK;
+}
+
+int WebSocketDeflateStream::ReadHandshakeResponse(
+ const CompletionCallback& callback) {
+ // TODO(yhirano) handshake related functions will be moved to somewhere.
+ NOTIMPLEMENTED();
+ return OK;
+}
+
+void WebSocketDeflateStream::OnReadComplete(
+ ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback,
+ int result) {
+ if (result != OK) {
+ frames->clear();
+ callback.Run(result);
+ return;
+ }
+
+ int r = InflateAndReadIfNecessary(frames, callback);
+ if (r != ERR_IO_PENDING)
+ callback.Run(r);
+}
+
+int WebSocketDeflateStream::Deflate(ScopedVector<WebSocketFrame>* frames) {
+ ScopedVector<WebSocketFrame> frames_to_write;
+ for (size_t i = 0; i < frames->size(); ++i) {
+ scoped_ptr<WebSocketFrame> frame((*frames)[i]);
+ (*frames)[i] = NULL;
+ DCHECK(!frame->header.reserved1);
+ if (!WebSocketFrameHeader::IsKnownDataOpCode(frame->header.opcode)) {
+ frames_to_write.push_back(frame.release());
+ continue;
+ }
+
+ if (writing_state_ == NOT_WRITING) {
+ current_writing_opcode_ = frame->header.opcode;
+ DCHECK(current_writing_opcode_ == WebSocketFrameHeader::kOpCodeText ||
+ current_writing_opcode_ == WebSocketFrameHeader::kOpCodeBinary);
+ // TODO(yhirano): For now, we unconditionally compress data messages.
+ // Further optimization is needed.
+ // http://crbug.com/163882
+ writing_state_ = WRITING_COMPRESSED_MESSAGE;
+ }
+ if (writing_state_ == WRITING_UNCOMPRESSED_MESSAGE) {
+ if (frame->header.final)
+ writing_state_ = NOT_WRITING;
+ frames_to_write.push_back(frame.release());
+ current_writing_opcode_ = WebSocketFrameHeader::kOpCodeContinuation;
+ } else {
+ DCHECK_EQ(WRITING_COMPRESSED_MESSAGE, writing_state_);
+ if (frame->data &&
+ !deflater_.AddBytes(frame->data->data(),
+ frame->header.payload_length)) {
+ DVLOG(1) << "WebSocket protocol error. "
+ << "deflater_.AddBytes() returns an error.";
+ return ERR_WS_PROTOCOL_ERROR;
+ }
+ if (frame->header.final && !deflater_.Finish()) {
+ DVLOG(1) << "WebSocket protocol error. "
+ << "deflater_.Finish() returns an error.";
+ return ERR_WS_PROTOCOL_ERROR;
+ }
+ if (deflater_.CurrentOutputSize() >= kChunkSize || frame->header.final) {
+ const WebSocketFrameHeader::OpCode opcode = current_writing_opcode_;
+ scoped_ptr<WebSocketFrame> compressed(new WebSocketFrame(opcode));
+ scoped_refptr<IOBufferWithSize> data =
+ deflater_.GetOutput(deflater_.CurrentOutputSize());
+ if (!data) {
+ DVLOG(1) << "WebSocket protocol error. "
+ << "deflater_.GetOutput() returns an error.";
+ return ERR_WS_PROTOCOL_ERROR;
+ }
+ compressed->header.CopyFrom(frame->header);
+ compressed->header.opcode = opcode;
+ compressed->header.final = frame->header.final;
+ compressed->header.reserved1 =
+ (opcode != WebSocketFrameHeader::kOpCodeContinuation);
+ compressed->data = data;
+ compressed->header.payload_length = data->size();
+
+ current_writing_opcode_ = WebSocketFrameHeader::kOpCodeContinuation;
+ frames_to_write.push_back(compressed.release());
+ }
+ if (frame->header.final)
+ writing_state_ = NOT_WRITING;
+ }
+ }
+ frames->swap(frames_to_write);
+ return OK;
+}
+
+int WebSocketDeflateStream::Inflate(ScopedVector<WebSocketFrame>* frames) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ ScopedVector<WebSocketFrame> frames_passed;
+ frames->swap(frames_passed);
+ for (size_t i = 0; i < frames_passed.size(); ++i) {
+ scoped_ptr<WebSocketFrame> frame(frames_passed[i]);
+ frames_passed[i] = NULL;
+ if (!WebSocketFrameHeader::IsKnownDataOpCode(frame->header.opcode)) {
+ frames_to_output.push_back(frame.release());
+ continue;
+ }
+
+ if (reading_state_ == NOT_READING) {
+ if (frame->header.reserved1)
+ reading_state_ = READING_COMPRESSED_MESSAGE;
+ else
+ reading_state_ = READING_UNCOMPRESSED_MESSAGE;
+ current_reading_opcode_ = frame->header.opcode;
+ } else {
+ if (frame->header.reserved1) {
+ DVLOG(1) << "WebSocket protocol error. "
+ << "Receiving a non-first frame with RSV1 flag set.";
+ return ERR_WS_PROTOCOL_ERROR;
+ }
+ }
+
+ if (reading_state_ == READING_UNCOMPRESSED_MESSAGE) {
+ if (frame->header.final)
+ reading_state_ = NOT_READING;
+ current_reading_opcode_ = WebSocketFrameHeader::kOpCodeContinuation;
+ frames_to_output.push_back(frame.release());
+ } else {
+ DCHECK_EQ(reading_state_, READING_COMPRESSED_MESSAGE);
+ if (frame->data &&
+ !inflater_.AddBytes(frame->data->data(),
+ frame->header.payload_length)) {
+ DVLOG(1) << "WebSocket protocol error. "
+ << "inflater_.AddBytes() returns an error.";
+ return ERR_WS_PROTOCOL_ERROR;
+ }
+ if (frame->header.final) {
+ if (!inflater_.Finish()) {
+ DVLOG(1) << "WebSocket protocol error. "
+ << "inflater_.Finish() returns an error.";
+ return ERR_WS_PROTOCOL_ERROR;
+ }
+ }
+ // TODO(yhirano): Many frames can be generated by the inflater and
+ // memory consumption can grow.
+ // We could avoid it, but avoiding it makes this class much more
+ // complicated.
+ while (inflater_.CurrentOutputSize() >= kChunkSize ||
+ frame->header.final) {
+ size_t size = std::min(kChunkSize, inflater_.CurrentOutputSize());
+ scoped_ptr<WebSocketFrame> inflated(
+ new WebSocketFrame(WebSocketFrameHeader::kOpCodeText));
+ scoped_refptr<IOBufferWithSize> data = inflater_.GetOutput(size);
+ bool is_final = !inflater_.CurrentOutputSize();
+ // |is_final| can't be true if |frame->header.final| is false.
+ DCHECK(!(is_final && !frame->header.final));
+ if (!data) {
+ DVLOG(1) << "WebSocket protocol error. "
+ << "inflater_.GetOutput() returns an error.";
+ return ERR_WS_PROTOCOL_ERROR;
+ }
+ inflated->header.CopyFrom(frame->header);
+ inflated->header.opcode = current_reading_opcode_;
+ inflated->header.final = is_final;
+ inflated->header.reserved1 = false;
+ inflated->data = data;
+ inflated->header.payload_length = data->size();
+
+ frames_to_output.push_back(inflated.release());
+ current_reading_opcode_ = WebSocketFrameHeader::kOpCodeContinuation;
+ if (is_final)
+ break;
+ }
+ if (frame->header.final)
+ reading_state_ = NOT_READING;
+ }
+ }
+ frames->swap(frames_to_output);
+ return frames->empty() ? ERR_IO_PENDING : OK;
+}
+
+int WebSocketDeflateStream::InflateAndReadIfNecessary(
+ ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback) {
+ int result = Inflate(frames);
+ while (result == ERR_IO_PENDING) {
+ DCHECK(frames->empty());
+ result = stream_->ReadFrames(frames, callback);
+ if (result < 0)
+ break;
+ DCHECK_EQ(OK, result);
+ DCHECK(!frames->empty());
+ result = Inflate(frames);
+ }
+ if (result < 0)
+ frames->clear();
+ return result;
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_deflate_stream.h b/net/websockets/websocket_deflate_stream.h
new file mode 100644
index 0000000..224bfeb
--- /dev/null
+++ b/net/websockets/websocket_deflate_stream.h
@@ -0,0 +1,90 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_DEFLATE_STREAM_H_
+#define NET_WEBSOCKETS_WEBSOCKET_DEFLATE_STREAM_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/websockets/websocket_deflater.h"
+#include "net/websockets/websocket_frame.h"
+#include "net/websockets/websocket_inflater.h"
+#include "net/websockets/websocket_stream.h"
+
+class GURL;
+
+namespace net {
+
+class HttpRequestHeaders;
+class HttpResponseInfo;
+
+// WebSocketDeflateStream is a WebSocketStream subclass.
+// WebSocketDeflateStream is for permessage-deflate WebSocket extension[1].
+//
+// [1]: http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-12
+class NET_EXPORT_PRIVATE WebSocketDeflateStream : public WebSocketStream {
+ public:
+ explicit WebSocketDeflateStream(scoped_ptr<WebSocketStream> stream);
+ virtual ~WebSocketDeflateStream();
+
+ // WebSocketStream functions.
+ virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual std::string GetSubProtocol() const OVERRIDE;
+ virtual std::string GetExtensions() const OVERRIDE;
+ virtual int SendHandshakeRequest(const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int ReadHandshakeResponse(const CompletionCallback& callback)
+ OVERRIDE;
+
+ private:
+ enum ReadingState {
+ READING_COMPRESSED_MESSAGE,
+ READING_UNCOMPRESSED_MESSAGE,
+ NOT_READING,
+ };
+
+ enum WritingState {
+ WRITING_COMPRESSED_MESSAGE,
+ WRITING_UNCOMPRESSED_MESSAGE,
+ NOT_WRITING,
+ };
+
+ void OnReadComplete(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback,
+ int result);
+
+ // This function deflates |frames| and stores the result to |frames| itself.
+ int Deflate(ScopedVector<WebSocketFrame>* frames);
+
+ // This function inflates |frames| and stores the result to |frames| itself.
+ int Inflate(ScopedVector<WebSocketFrame>* frames);
+
+ int InflateAndReadIfNecessary(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback);
+
+ const scoped_ptr<WebSocketStream> stream_;
+ WebSocketDeflater deflater_;
+ WebSocketInflater inflater_;
+ ReadingState reading_state_;
+ WritingState writing_state_;
+ WebSocketFrameHeader::OpCode current_reading_opcode_;
+ WebSocketFrameHeader::OpCode current_writing_opcode_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketDeflateStream);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_DEFLATE_STREAM_H_
diff --git a/net/websockets/websocket_deflate_stream_test.cc b/net/websockets/websocket_deflate_stream_test.cc
new file mode 100644
index 0000000..63955f1
--- /dev/null
+++ b/net/websockets/websocket_deflate_stream_test.cc
@@ -0,0 +1,934 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/websockets/websocket_deflate_stream.h"
+
+#include <stdint.h>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/websockets/websocket_deflater.h"
+#include "net/websockets/websocket_frame.h"
+#include "net/websockets/websocket_inflater.h"
+#include "net/websockets/websocket_stream.h"
+#include "net/websockets/websocket_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class HttpResponseInfo;
+
+namespace {
+
+typedef testing::MockFunction<void(int)> MockCallback; // NOLINT
+using testing::_;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+
+typedef uint32_t FrameFlag;
+const FrameFlag kNoFlag = 0;
+const FrameFlag kFinal = 1;
+const FrameFlag kReserved1 = 2;
+// We don't define values for other flags because we don't need them.
+
+// The value must equal to the value of the corresponding
+// constant in websocket_deflate_stream.cc
+const size_t kChunkSize = 4 * 1024;
+const int kWindowBits = 15;
+
+scoped_refptr<IOBuffer> ToIOBuffer(const std::string& s) {
+ scoped_refptr<IOBuffer> buffer = new IOBuffer(s.size());
+ memcpy(buffer->data(), s.data(), s.size());
+ return buffer;
+}
+
+std::string ToString(IOBufferWithSize* buffer) {
+ return std::string(buffer->data(), buffer->size());
+}
+
+std::string ToString(const scoped_refptr<IOBufferWithSize>& buffer) {
+ return ToString(buffer.get());
+}
+
+std::string ToString(IOBuffer* buffer, size_t size) {
+ return std::string(buffer->data(), size);
+}
+
+std::string ToString(const scoped_refptr<IOBuffer>& buffer, size_t size) {
+ return ToString(buffer.get(), size);
+}
+
+std::string ToString(WebSocketFrame* frame) {
+ return frame->data ? ToString(frame->data, frame->header.payload_length) : "";
+}
+
+void AppendTo(ScopedVector<WebSocketFrame>* frames,
+ WebSocketFrameHeader::OpCode opcode,
+ FrameFlag flag,
+ const std::string& data) {
+ scoped_ptr<WebSocketFrame> frame(new WebSocketFrame(opcode));
+ frame->header.final = (flag & kFinal);
+ frame->header.reserved1 = (flag & kReserved1);
+ frame->data = ToIOBuffer(data);
+ frame->header.payload_length = data.size();
+ frames->push_back(frame.release());
+}
+
+void AppendTo(ScopedVector<WebSocketFrame>* frames,
+ WebSocketFrameHeader::OpCode opcode,
+ FrameFlag flag) {
+ scoped_ptr<WebSocketFrame> frame(new WebSocketFrame(opcode));
+ frame->header.final = (flag & kFinal);
+ frame->header.reserved1 = (flag & kReserved1);
+ frames->push_back(frame.release());
+}
+
+class MockWebSocketStream : public WebSocketStream {
+ public:
+ MOCK_METHOD2(ReadFrames, int(ScopedVector<WebSocketFrame>*,
+ const CompletionCallback&));
+ MOCK_METHOD2(WriteFrames, int(ScopedVector<WebSocketFrame>*,
+ const CompletionCallback&));
+ MOCK_METHOD0(Close, void());
+ MOCK_CONST_METHOD0(GetSubProtocol, std::string());
+ MOCK_CONST_METHOD0(GetExtensions, std::string());
+ MOCK_METHOD4(SendHandshakeRequest, int(const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback));
+ MOCK_METHOD1(ReadHandshakeResponse, int(const CompletionCallback& callback));
+};
+
+class WebSocketDeflateStreamTest : public ::testing::Test {
+ public:
+ WebSocketDeflateStreamTest()
+ : mock_stream_(NULL) {
+ mock_stream_ = new testing::StrictMock<MockWebSocketStream>;
+ deflate_stream_.reset(new WebSocketDeflateStream(
+ scoped_ptr<WebSocketStream>(mock_stream_)));
+ }
+ virtual ~WebSocketDeflateStreamTest() {}
+
+ protected:
+ scoped_ptr<WebSocketDeflateStream> deflate_stream_;
+ // |mock_stream_| will be deleted when |deflate_stream_| is destroyed.
+ MockWebSocketStream* mock_stream_;
+};
+
+// ReadFrameStub is a stub for WebSocketStream::ReadFrames.
+// It returns |result_| and |frames_to_output_| to the caller and
+// saves parameters to |frames_passed_| and |callback_|.
+class ReadFramesStub {
+ public:
+ explicit ReadFramesStub(int result) : result_(result) {}
+
+ ReadFramesStub(int result, ScopedVector<WebSocketFrame>* frames_to_output)
+ : result_(result) {
+ frames_to_output_.swap(*frames_to_output);
+ }
+
+ int Call(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback) {
+ DCHECK(frames->empty());
+ frames_passed_ = frames;
+ callback_ = callback;
+ frames->swap(frames_to_output_);
+ return result_;
+ }
+
+ int result() const { return result_; }
+ const CompletionCallback callback() const { return callback_; }
+ ScopedVector<WebSocketFrame>* frames_passed() {
+ return frames_passed_;
+ }
+
+ private:
+ int result_;
+ CompletionCallback callback_;
+ ScopedVector<WebSocketFrame> frames_to_output_;
+ ScopedVector<WebSocketFrame>* frames_passed_;
+};
+
+// WriteFrameStub is a stub for WebSocketStream::WriteFrames.
+// It returns |result_| and |frames_| to the caller and
+// saves |callback| parameter to |callback_|.
+class WriteFramesStub {
+ public:
+ explicit WriteFramesStub(int result) : result_(result) {}
+
+ int Call(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback) {
+ for (size_t i = 0; i < frames->size(); ++i) {
+ frames_.push_back((*frames)[i]);
+ }
+ frames->weak_clear();
+ callback_ = callback;
+ return result_;
+ }
+
+ int result() const { return result_; }
+ const CompletionCallback callback() const { return callback_; }
+ ScopedVector<WebSocketFrame>* frames() { return &frames_; }
+
+ private:
+ int result_;
+ CompletionCallback callback_;
+ ScopedVector<WebSocketFrame> frames_;
+};
+
+TEST_F(WebSocketDeflateStreamTest, ReadFailedImmediately) {
+ ScopedVector<WebSocketFrame> frames;
+ CompletionCallback callback;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Return(ERR_FAILED));
+ }
+ EXPECT_EQ(ERR_FAILED, deflate_stream_->ReadFrames(&frames, callback));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadUncompressedFrameImmediately) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal,
+ "hello");
+ ReadFramesStub stub(OK, &frames_to_output);
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ CompletionCallback callback;
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(1u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("hello", ToString(frames[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadUncompressedFrameAsync) {
+ ReadFramesStub stub(ERR_IO_PENDING);
+ ScopedVector<WebSocketFrame> frames;
+ MockCallback mock_callback, checkpoint;
+ CompletionCallback callback =
+ base::Bind(&MockCallback::Call, base::Unretained(&mock_callback));
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(mock_callback, Call(OK));
+ }
+ ASSERT_EQ(ERR_IO_PENDING, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(0u, frames.size());
+
+ checkpoint.Call(0);
+
+ AppendTo(stub.frames_passed(),
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal,
+ "hello");
+ stub.callback().Run(OK);
+ ASSERT_EQ(1u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("hello", ToString(frames[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadFailedAsync) {
+ ReadFramesStub stub(ERR_IO_PENDING);
+ ScopedVector<WebSocketFrame> frames;
+ MockCallback mock_callback, checkpoint;
+ CompletionCallback callback =
+ base::Bind(&MockCallback::Call, base::Unretained(&mock_callback));
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(mock_callback, Call(ERR_FAILED));
+ }
+ ASSERT_EQ(ERR_IO_PENDING, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(0u, frames.size());
+
+ checkpoint.Call(0);
+
+ AppendTo(stub.frames_passed(),
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal,
+ "hello");
+ stub.callback().Run(ERR_FAILED);
+ ASSERT_EQ(0u, frames.size());
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadCompressedFrameImmediately) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal | kReserved1,
+ std::string("\xf2\x48\xcd\xc9\xc9\x07\x00", 7));
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(1u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("Hello", ToString(frames[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadCompressedFrameAsync) {
+ ReadFramesStub stub(ERR_IO_PENDING);
+ MockCallback mock_callback, checkpoint;
+ CompletionCallback callback =
+ base::Bind(&MockCallback::Call, base::Unretained(&mock_callback));
+ ScopedVector<WebSocketFrame> frames;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(mock_callback, Call(OK));
+ }
+ ASSERT_EQ(ERR_IO_PENDING, deflate_stream_->ReadFrames(&frames, callback));
+
+ checkpoint.Call(0);
+
+ AppendTo(stub.frames_passed(),
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal | kReserved1,
+ std::string("\xf2\x48\xcd\xc9\xc9\x07\x00", 7));
+ stub.callback().Run(OK);
+
+ ASSERT_EQ(1u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("Hello", ToString(frames[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest,
+ ReadCompressedFrameFragmentImmediatelyButInflaterReturnsPending) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ const std::string data1("\xf2", 1);
+ const std::string data2("\x48\xcd\xc9\xc9\x07\x00", 6);
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kReserved1,
+ data1);
+ ReadFramesStub stub1(OK, &frames_to_output), stub2(ERR_IO_PENDING);
+ MockCallback mock_callback, checkpoint;
+ CompletionCallback callback =
+ base::Bind(&MockCallback::Call, base::Unretained(&mock_callback));
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub1, &ReadFramesStub::Call))
+ .WillOnce(Invoke(&stub2, &ReadFramesStub::Call));
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(mock_callback, Call(OK));
+ }
+ ASSERT_EQ(ERR_IO_PENDING, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(0u, frames.size());
+
+ AppendTo(stub2.frames_passed(),
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal,
+ data2);
+
+ checkpoint.Call(0);
+ stub2.callback().Run(OK);
+
+ ASSERT_EQ(1u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("Hello", ToString(frames[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadInvalidCompressedPayload) {
+ const std::string data("\xf2\x48\xcdINVALID", 10);
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal | kReserved1,
+ data);
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(ERR_WS_PROTOCOL_ERROR,
+ deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(0u, frames.size());
+}
+
+TEST_F(WebSocketDeflateStreamTest, MergeMultipleFramesInReadFrames) {
+ const std::string data1("\xf2\x48\xcd", 3);
+ const std::string data2("\xc9\xc9\x07\x00", 4);
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kReserved1,
+ data1);
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ kFinal,
+ data2);
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(1u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("Hello", ToString(frames[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadUncompressedEmptyFrames) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kNoFlag);
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ kFinal);
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(2u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_FALSE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("", ToString(frames[0]));
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeContinuation,
+ frames[1]->header.opcode);
+ EXPECT_TRUE(frames[1]->header.final);
+ EXPECT_FALSE(frames[1]->header.reserved1);
+ EXPECT_EQ("", ToString(frames[1]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadCompressedEmptyFrames) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kReserved1,
+ std::string("\x02\x00", 1));
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ kFinal);
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(1u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("", ToString(frames[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest,
+ ReadCompressedFrameFollowedByEmptyFrame) {
+ const std::string data("\xf2\x48\xcd\xc9\xc9\x07\x00", 7);
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kReserved1,
+ data);
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ kFinal);
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(1u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("Hello", ToString(frames[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadControlFrameBetweenDataFrames) {
+ const std::string data1("\xf2\x48\xcd", 3);
+ const std::string data2("\xc9\xc9\x07\x00", 4);
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kReserved1,
+ data1);
+ AppendTo(&frames_to_output, WebSocketFrameHeader::kOpCodePing, kFinal);
+ AppendTo(&frames_to_output, WebSocketFrameHeader::kOpCodeText, kFinal, data2);
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(2u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodePing, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[1]->header.opcode);
+ EXPECT_TRUE(frames[1]->header.final);
+ EXPECT_FALSE(frames[1]->header.reserved1);
+ EXPECT_EQ("Hello", ToString(frames[1]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, SplitToMultipleFramesInReadFrames) {
+ WebSocketDeflater deflater(WebSocketDeflater::TAKE_OVER_CONTEXT);
+ deflater.Initialize(kWindowBits);
+ const size_t kSize = kChunkSize * 3;
+ const std::string original_data(kSize, 'a');
+ deflater.AddBytes(original_data.data(), original_data.size());
+ deflater.Finish();
+
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeBinary,
+ kFinal | kReserved1,
+ ToString(deflater.GetOutput(deflater.CurrentOutputSize())));
+
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(3u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeBinary, frames[0]->header.opcode);
+ EXPECT_FALSE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ(kChunkSize, static_cast<size_t>(frames[0]->header.payload_length));
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeContinuation,
+ frames[1]->header.opcode);
+ EXPECT_FALSE(frames[1]->header.final);
+ EXPECT_FALSE(frames[1]->header.reserved1);
+ EXPECT_EQ(kChunkSize, static_cast<size_t>(frames[1]->header.payload_length));
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeContinuation,
+ frames[2]->header.opcode);
+ EXPECT_TRUE(frames[2]->header.final);
+ EXPECT_FALSE(frames[2]->header.reserved1);
+ EXPECT_EQ(kChunkSize, static_cast<size_t>(frames[2]->header.payload_length));
+ EXPECT_EQ(original_data,
+ ToString(frames[0]) + ToString(frames[1]) + ToString(frames[2]));
+}
+
+TEST_F(WebSocketDeflateStreamTest,
+ Reserved1TurnsOnDuringReadingCompressedContinuationFrame) {
+ const std::string data1("\xf2\x48\xcd", 3);
+ const std::string data2("\xc9\xc9\x07\x00", 4);
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kReserved1,
+ data1);
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ kFinal | kReserved1,
+ data2);
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(ERR_WS_PROTOCOL_ERROR,
+ deflate_stream_->ReadFrames(&frames, callback));
+}
+
+TEST_F(WebSocketDeflateStreamTest,
+ Reserved1TurnsOnDuringReadingUncompressedContinuationFrame) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kNoFlag,
+ "hello");
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ kFinal | kReserved1,
+ "world");
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(ERR_WS_PROTOCOL_ERROR,
+ deflate_stream_->ReadFrames(&frames, callback));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadCompressedMessages) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal | kReserved1,
+ std::string(
+ "\x4a\xce\xcf\x2d\x28\x4a\x2d\x2e\x4e\x4d\x31\x04\x00", 13));
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal | kReserved1,
+ std::string("\x4a\x86\x33\x8d\x00\x00", 6));
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(2u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("compressed1", ToString(frames[0]));
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[1]->header.opcode);
+ EXPECT_TRUE(frames[1]->header.final);
+ EXPECT_FALSE(frames[1]->header.reserved1);
+ EXPECT_EQ("compressed2", ToString(frames[1]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, ReadUncompressedMessages) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal,
+ "uncompressed1");
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal,
+ "uncompressed2");
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(2u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("uncompressed1", ToString(frames[0]));
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[1]->header.opcode);
+ EXPECT_TRUE(frames[1]->header.final);
+ EXPECT_FALSE(frames[1]->header.reserved1);
+ EXPECT_EQ("uncompressed2", ToString(frames[1]));
+}
+
+TEST_F(WebSocketDeflateStreamTest,
+ ReadCompressedMessageThenUncompressedMessage) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal | kReserved1,
+ std::string(
+ "\x4a\xce\xcf\x2d\x28\x4a\x2d\x2e\x4e\x4d\x01\x00", 12));
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal,
+ "uncompressed");
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(2u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("compressed", ToString(frames[0]));
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[1]->header.opcode);
+ EXPECT_TRUE(frames[1]->header.final);
+ EXPECT_FALSE(frames[1]->header.reserved1);
+ EXPECT_EQ("uncompressed", ToString(frames[1]));
+}
+
+TEST_F(WebSocketDeflateStreamTest,
+ ReadUncompressedMessageThenCompressedMessage) {
+ ScopedVector<WebSocketFrame> frames_to_output;
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal,
+ "uncompressed");
+ AppendTo(&frames_to_output,
+ WebSocketFrameHeader::kOpCodeText,
+ kFinal | kReserved1,
+ std::string(
+ "\x4a\xce\xcf\x2d\x28\x4a\x2d\x2e\x4e\x4d\x01\x00", 12));
+ ReadFramesStub stub(OK, &frames_to_output);
+ CompletionCallback callback;
+ ScopedVector<WebSocketFrame> frames;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, ReadFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &ReadFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->ReadFrames(&frames, callback));
+ ASSERT_EQ(2u, frames.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[0]->header.opcode);
+ EXPECT_TRUE(frames[0]->header.final);
+ EXPECT_FALSE(frames[0]->header.reserved1);
+ EXPECT_EQ("uncompressed", ToString(frames[0]));
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames[1]->header.opcode);
+ EXPECT_TRUE(frames[1]->header.final);
+ EXPECT_FALSE(frames[1]->header.reserved1);
+ EXPECT_EQ("compressed", ToString(frames[1]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, WriteEmpty) {
+ ScopedVector<WebSocketFrame> frames;
+ CompletionCallback callback;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(&frames, _)).Times(0);
+ }
+ EXPECT_EQ(OK, deflate_stream_->WriteFrames(&frames, callback));
+}
+
+TEST_F(WebSocketDeflateStreamTest, WriteFailedImmediately) {
+ ScopedVector<WebSocketFrame> frames;
+ CompletionCallback callback;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(&frames, _))
+ .WillOnce(Return(ERR_FAILED));
+ }
+
+ AppendTo(&frames, WebSocketFrameHeader::kOpCodeText, kFinal, "hello");
+ EXPECT_EQ(ERR_FAILED, deflate_stream_->WriteFrames(&frames, callback));
+}
+
+TEST_F(WebSocketDeflateStreamTest, WriteFrameImmediately) {
+ ScopedVector<WebSocketFrame> frames;
+ CompletionCallback callback;
+ WriteFramesStub stub(OK);
+ AppendTo(&frames, WebSocketFrameHeader::kOpCodeText, kFinal, "Hello");
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(_, _))
+ .WillOnce(Invoke(&stub, &WriteFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->WriteFrames(&frames, callback));
+ const ScopedVector<WebSocketFrame>& frames_passed = *stub.frames();
+ ASSERT_EQ(1u, frames_passed.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_passed[0]->header.opcode);
+ EXPECT_TRUE(frames_passed[0]->header.final);
+ EXPECT_TRUE(frames_passed[0]->header.reserved1);
+ EXPECT_EQ(std::string("\xf2\x48\xcd\xc9\xc9\x07\x00", 7),
+ ToString(frames_passed[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, WriteFrameAsync) {
+ WriteFramesStub stub(ERR_IO_PENDING);
+ MockCallback mock_callback, checkpoint;
+ CompletionCallback callback =
+ base::Bind(&MockCallback::Call, base::Unretained(&mock_callback));
+ ScopedVector<WebSocketFrame> frames;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &WriteFramesStub::Call));
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(mock_callback, Call(OK));
+ }
+ AppendTo(&frames, WebSocketFrameHeader::kOpCodeText, kFinal, "Hello");
+ ASSERT_EQ(ERR_IO_PENDING, deflate_stream_->WriteFrames(&frames, callback));
+
+ checkpoint.Call(0);
+ stub.callback().Run(OK);
+
+ const ScopedVector<WebSocketFrame>& frames_passed = *stub.frames();
+ ASSERT_EQ(1u, frames_passed.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_passed[0]->header.opcode);
+ EXPECT_TRUE(frames_passed[0]->header.final);
+ EXPECT_TRUE(frames_passed[0]->header.reserved1);
+ EXPECT_EQ(std::string("\xf2\x48\xcd\xc9\xc9\x07\x00", 7),
+ ToString(frames_passed[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, WriteControlFrameBetweenDataFrames) {
+ ScopedVector<WebSocketFrame> frames;
+ AppendTo(&frames, WebSocketFrameHeader::kOpCodeText, kNoFlag, "Hel");
+ AppendTo(&frames, WebSocketFrameHeader::kOpCodePing, kFinal);
+ AppendTo(&frames, WebSocketFrameHeader::kOpCodeContinuation, kFinal, "lo");
+ WriteFramesStub stub(OK);
+ CompletionCallback callback;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &WriteFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->WriteFrames(&frames, callback));
+ const ScopedVector<WebSocketFrame>& frames_passed = *stub.frames();
+ ASSERT_EQ(2u, frames_passed.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodePing, frames_passed[0]->header.opcode);
+ EXPECT_TRUE(frames_passed[0]->header.final);
+ EXPECT_FALSE(frames_passed[0]->header.reserved1);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_passed[1]->header.opcode);
+ EXPECT_TRUE(frames_passed[1]->header.final);
+ EXPECT_TRUE(frames_passed[1]->header.reserved1);
+ EXPECT_EQ(std::string("\xf2\x48\xcd\xc9\xc9\x07\x00", 7),
+ ToString(frames_passed[1]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, WriteEmptyMessage) {
+ ScopedVector<WebSocketFrame> frames;
+ AppendTo(&frames, WebSocketFrameHeader::kOpCodeText, kFinal);
+ WriteFramesStub stub(OK);
+ CompletionCallback callback;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(&frames, _))
+ .WillOnce(Invoke(&stub, &WriteFramesStub::Call));
+ }
+ ASSERT_EQ(OK, deflate_stream_->WriteFrames(&frames, callback));
+ const ScopedVector<WebSocketFrame>& frames_passed = *stub.frames();
+ ASSERT_EQ(1u, frames_passed.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_passed[0]->header.opcode);
+ EXPECT_TRUE(frames_passed[0]->header.final);
+ EXPECT_TRUE(frames_passed[0]->header.reserved1);
+ EXPECT_EQ(std::string("\x02\x00", 2), ToString(frames_passed[0]));
+}
+
+TEST_F(WebSocketDeflateStreamTest, LargeDeflatedFramesShouldBeSplit) {
+ WebSocketDeflater deflater(WebSocketDeflater::TAKE_OVER_CONTEXT);
+ LinearCongruentialGenerator lcg(133);
+ WriteFramesStub stub(OK);
+ CompletionCallback callback;
+ const size_t size = 1024;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(_, _))
+ .WillRepeatedly(Invoke(&stub, &WriteFramesStub::Call));
+ }
+ ScopedVector<WebSocketFrame> total_compressed_frames;
+
+ deflater.Initialize(kWindowBits);
+ while (true) {
+ bool is_final = (total_compressed_frames.size() >= 2);
+ ScopedVector<WebSocketFrame> frames;
+ std::string data;
+ for (size_t i = 0; i < size; ++i)
+ data += static_cast<char>(lcg.Generate());
+ deflater.AddBytes(data.data(), data.size());
+ FrameFlag flag = is_final ? kFinal : kNoFlag;
+ AppendTo(&frames, WebSocketFrameHeader::kOpCodeBinary, flag, data);
+ ASSERT_EQ(OK, deflate_stream_->WriteFrames(&frames, callback));
+ for (size_t i = 0; i < stub.frames()->size(); ++i)
+ total_compressed_frames.push_back((*stub.frames())[i]);
+ stub.frames()->weak_clear();
+ if (is_final)
+ break;
+ }
+ deflater.Finish();
+ std::string total_deflated;
+ for (size_t i = 0; i < total_compressed_frames.size(); ++i) {
+ WebSocketFrame* frame = total_compressed_frames[i];
+ const WebSocketFrameHeader& header = frame->header;
+ if (i > 0) {
+ EXPECT_EQ(header.kOpCodeContinuation, header.opcode);
+ EXPECT_FALSE(header.reserved1);
+ } else {
+ EXPECT_EQ(header.kOpCodeBinary, header.opcode);
+ EXPECT_TRUE(header.reserved1);
+ }
+ const bool is_final_frame = (i + 1 == total_compressed_frames.size());
+ EXPECT_EQ(is_final_frame, header.final);
+ if (!is_final_frame)
+ EXPECT_GT(header.payload_length, 0ul);
+ total_deflated += ToString(frame);
+ }
+ EXPECT_EQ(total_deflated,
+ ToString(deflater.GetOutput(deflater.CurrentOutputSize())));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/websockets/websocket_inflater_test.cc b/net/websockets/websocket_inflater_test.cc
index 722e29e8..139e901 100644
--- a/net/websockets/websocket_inflater_test.cc
+++ b/net/websockets/websocket_inflater_test.cc
@@ -11,6 +11,7 @@
#include "base/memory/ref_counted.h"
#include "net/base/io_buffer.h"
#include "net/websockets/websocket_deflater.h"
+#include "net/websockets/websocket_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
@@ -21,28 +22,6 @@ std::string ToString(IOBufferWithSize* buffer) {
return std::string(buffer->data(), buffer->size());
}
-class LinearCongruentialGenerator {
- public:
- explicit LinearCongruentialGenerator(uint32_t seed);
- uint32_t Generate();
-
- private:
- uint32_t current_;
-
- static const uint32_t a_ = 1103515245;
- static const uint32_t c_ = 12345;
- static const uint32_t m_ = 1 << 31;
-};
-
-LinearCongruentialGenerator::LinearCongruentialGenerator(uint32_t seed)
- : current_(seed) {}
-
-uint32_t LinearCongruentialGenerator::Generate() {
- uint32_t result = current_;
- current_ = (current_ * a_ + c_) % m_;
- return result;
-}
-
TEST(WebSocketInflaterTest, Construct) {
WebSocketInflater inflater;
ASSERT_TRUE(inflater.Initialize(15));
diff --git a/net/websockets/websocket_test_util.cc b/net/websockets/websocket_test_util.cc
new file mode 100644
index 0000000..350a696
--- /dev/null
+++ b/net/websockets/websocket_test_util.cc
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/websockets/websocket_test_util.h"
+
+#include "base/basictypes.h"
+
+namespace net {
+
+namespace {
+const uint64 kA =
+ (static_cast<uint64>(0x5851f42d) << 32) + static_cast<uint64>(0x4c957f2d);
+const uint64 kC = 12345;
+const uint64 kM = static_cast<uint64>(1) << 48;
+
+} // namespace
+
+LinearCongruentialGenerator::LinearCongruentialGenerator(uint32 seed)
+ : current_(seed) {}
+
+uint32 LinearCongruentialGenerator::Generate() {
+ uint64 result = current_;
+ current_ = (current_ * kA + kC) % kM;
+ return static_cast<uint32>(result >> 16);
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_test_util.h b/net/websockets/websocket_test_util.h
new file mode 100644
index 0000000..1866e2a
--- /dev/null
+++ b/net/websockets/websocket_test_util.h
@@ -0,0 +1,23 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_TEST_UTIL_H_
+#define NET_WEBSOCKETS_WEBSOCKET_TEST_UTIL_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+
+class LinearCongruentialGenerator {
+ public:
+ explicit LinearCongruentialGenerator(uint32 seed);
+ uint32 Generate();
+
+ private:
+ uint64 current_;
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_TEST_UTIL_H_