diff options
author | yhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-11 06:04:11 +0000 |
---|---|---|
committer | yhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-11 06:04:11 +0000 |
commit | ce9f7ffd61434ece2521087e1aeb68d9635a6700 (patch) | |
tree | 24c798902a7a2816a56798717c67814ab3d6afa8 /net | |
parent | a4b7df33bda0468e476389d38230669e9f6169e6 (diff) | |
download | chromium_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.gyp | 7 | ||||
-rw-r--r-- | net/websockets/README | 7 | ||||
-rw-r--r-- | net/websockets/websocket_deflate_stream.cc | 290 | ||||
-rw-r--r-- | net/websockets/websocket_deflate_stream.h | 90 | ||||
-rw-r--r-- | net/websockets/websocket_deflate_stream_test.cc | 934 | ||||
-rw-r--r-- | net/websockets/websocket_inflater_test.cc | 23 | ||||
-rw-r--r-- | net/websockets/websocket_test_util.cc | 28 | ||||
-rw-r--r-- | net/websockets/websocket_test_util.h | 23 |
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_ |