diff options
author | yhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-20 03:34:57 +0000 |
---|---|---|
committer | yhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-20 03:34:57 +0000 |
commit | 07e045b750d46ac26d4ef960d5d44e252f892a2d (patch) | |
tree | 7e5c820f530ee9903760735b6ca8f9ffd920de45 /net/websockets | |
parent | 741fba42846459cbecdeb933f98e74bffdc0e306 (diff) | |
download | chromium_src-07e045b750d46ac26d4ef960d5d44e252f892a2d.zip chromium_src-07e045b750d46ac26d4ef960d5d44e252f892a2d.tar.gz chromium_src-07e045b750d46ac26d4ef960d5d44e252f892a2d.tar.bz2 |
Introduce WebSocketInflater.
Implement WebSocketInflater, a utility class for the permessage-deflate WebSocket extension.
BUG=280910
R=ricea, tyoshino
Review URL: https://chromiumcodereview.appspot.com/23480049
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@224271 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/websockets')
-rw-r--r-- | net/websockets/README | 3 | ||||
-rw-r--r-- | net/websockets/websocket_inflater.cc | 280 | ||||
-rw-r--r-- | net/websockets/websocket_inflater.h | 130 | ||||
-rw-r--r-- | net/websockets/websocket_inflater_test.cc | 245 |
4 files changed, 658 insertions, 0 deletions
diff --git a/net/websockets/README b/net/websockets/README index 1d1e1c3..e9fd214f 100644 --- a/net/websockets/README +++ b/net/websockets/README @@ -53,6 +53,9 @@ websocket_frame_parser.cc websocket_frame_parser.h websocket_frame_parser_test.cc websocket_frame_test.cc +websocket_inflater.cc +websocket_inflater.h +websocket_inflater_test.cc websocket_mux.h websocket_stream_base.h websocket_stream.cc diff --git a/net/websockets/websocket_inflater.cc b/net/websockets/websocket_inflater.cc new file mode 100644 index 0000000..fbfb079 --- /dev/null +++ b/net/websockets/websocket_inflater.cc @@ -0,0 +1,280 @@ +// 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_inflater.h" + +#include <algorithm> +#include <deque> +#include <vector> + +#include "base/logging.h" +#include "net/base/io_buffer.h" +#include "third_party/zlib/zlib.h" + +namespace net { + +namespace { + +class ShrinkableIOBufferWithSize : public IOBufferWithSize { + public: + explicit ShrinkableIOBufferWithSize(int size) + : IOBufferWithSize(size) {} + + void Shrink(int new_size) { + DCHECK_LE(new_size, size_); + size_ = new_size; + } + + private: + virtual ~ShrinkableIOBufferWithSize() {} +}; + +} // namespace + +WebSocketInflater::WebSocketInflater() + : input_queue_(kDefaultInputIOBufferCapacity), + output_buffer_(kDefaultBufferCapacity) {} + +WebSocketInflater::WebSocketInflater(size_t input_queue_capacity, + size_t output_buffer_capacity) + : input_queue_(input_queue_capacity), + output_buffer_(output_buffer_capacity) { + DCHECK_GT(input_queue_capacity, 0u); + DCHECK_GT(output_buffer_capacity, 0u); +} + +bool WebSocketInflater::Initialize(int window_bits) { + DCHECK_LE(8, window_bits); + DCHECK_GE(15, window_bits); + stream_.reset(new z_stream); + memset(stream_.get(), 0, sizeof(*stream_)); + int result = inflateInit2(stream_.get(), -window_bits); + if (result != Z_OK) { + inflateEnd(stream_.get()); + stream_.reset(); + return false; + } + return true; +} + +WebSocketInflater::~WebSocketInflater() { + if (stream_) { + inflateEnd(stream_.get()); + stream_.reset(); + } +} + +bool WebSocketInflater::AddBytes(const char* data, size_t size) { + if (!size) + return true; + + if (!input_queue_.IsEmpty()) { + // choked + input_queue_.Push(data, size); + return true; + } + + int result = InflateWithFlush(data, size); + if (stream_->avail_in > 0) + input_queue_.Push(&data[size - stream_->avail_in], stream_->avail_in); + + return result == Z_OK || result == Z_BUF_ERROR; +} + +bool WebSocketInflater::Finish() { + return AddBytes("\x00\x00\xff\xff", 4); +} + +scoped_refptr<IOBufferWithSize> WebSocketInflater::GetOutput(size_t size) { + scoped_refptr<ShrinkableIOBufferWithSize> buffer = + new ShrinkableIOBufferWithSize(size); + size_t num_bytes_copied = 0; + + while (num_bytes_copied < size && output_buffer_.Size() > 0) { + size_t num_bytes_to_copy = + std::min(output_buffer_.Size(), size - num_bytes_copied); + output_buffer_.Read(&buffer->data()[num_bytes_copied], num_bytes_to_copy); + num_bytes_copied += num_bytes_to_copy; + int result = InflateChokedInput(); + if (result != Z_OK && result != Z_BUF_ERROR) + return NULL; + } + buffer->Shrink(num_bytes_copied); + return buffer; +} + +int WebSocketInflater::InflateWithFlush(const char* next_in, size_t avail_in) { + int result = Inflate(next_in, avail_in, Z_NO_FLUSH); + if (result != Z_OK && result != Z_BUF_ERROR) + return result; + + if (CurrentOutputSize() > 0) + return result; + // CurrentOutputSize() == 0 means there is no data to be output, + // so we should make sure it by using Z_SYNC_FLUSH. + return Inflate(reinterpret_cast<const char*>(stream_->next_in), + stream_->avail_in, + Z_SYNC_FLUSH); +} + +int WebSocketInflater::Inflate(const char* next_in, + size_t avail_in, + int flush) { + stream_->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(next_in)); + stream_->avail_in = avail_in; + + int result = Z_BUF_ERROR; + do { + std::pair<char*, size_t> tail = output_buffer_.GetTail(); + if (!tail.second) + break; + + stream_->next_out = reinterpret_cast<Bytef*>(tail.first); + stream_->avail_out = tail.second; + result = inflate(stream_.get(), flush); + output_buffer_.AdvanceTail(tail.second - stream_->avail_out); + if (result == Z_STREAM_END) { + // Received a block with BFINAL set to 1. Reset the decompression state. + result = inflateReset(stream_.get()); + } else if (tail.second == stream_->avail_out) { + break; + } + } while (result == Z_OK || result == Z_BUF_ERROR); + return result; +} + +int WebSocketInflater::InflateChokedInput() { + if (input_queue_.IsEmpty()) + return InflateWithFlush(NULL, 0); + + int result = Z_BUF_ERROR; + while (!input_queue_.IsEmpty()) { + std::pair<char*, size_t> top = input_queue_.Top(); + + result = InflateWithFlush(top.first, top.second); + input_queue_.Consume(top.second - stream_->avail_in); + + if (result != Z_OK && result != Z_BUF_ERROR) + return result; + + if (stream_->avail_in > 0) { + // There are some data which are not consumed. + break; + } + } + return result; +} + +WebSocketInflater::OutputBuffer::OutputBuffer(size_t capacity) + : capacity_(capacity), + buffer_(capacity_ + 1), // 1 for sentinel + head_(0), + tail_(0) {} + +WebSocketInflater::OutputBuffer::~OutputBuffer() {} + +size_t WebSocketInflater::OutputBuffer::Size() const { + return (tail_ + buffer_.size() - head_) % buffer_.size(); +} + +std::pair<char*, size_t> WebSocketInflater::OutputBuffer::GetTail() { + return std::make_pair(&buffer_[tail_], + std::min(capacity_ - Size(), buffer_.size() - tail_)); +} + +void WebSocketInflater::OutputBuffer::Read(char* dest, size_t size) { + DCHECK_LE(size, Size()); + + size_t num_bytes_copied = 0; + if (tail_ < head_) { + size_t num_bytes_to_copy = std::min(size, buffer_.size() - head_); + memcpy(&dest[num_bytes_copied], &buffer_[head_], num_bytes_to_copy); + AdvanceHead(num_bytes_to_copy); + num_bytes_copied += num_bytes_to_copy; + } + + if (num_bytes_copied == size) + return; + DCHECK_LE(head_, tail_); + size_t num_bytes_to_copy = size - num_bytes_copied; + DCHECK_LE(num_bytes_to_copy, tail_ - head_); + memcpy(&dest[num_bytes_copied], &buffer_[head_], num_bytes_to_copy); + AdvanceHead(num_bytes_to_copy); + num_bytes_copied += num_bytes_to_copy; + DCHECK_EQ(size, num_bytes_copied); + return; +} + +void WebSocketInflater::OutputBuffer::AdvanceHead(size_t advance) { + DCHECK_LE(advance, Size()); + head_ = (head_ + advance) % buffer_.size(); +} + +void WebSocketInflater::OutputBuffer::AdvanceTail(size_t advance) { + DCHECK_LE(advance + Size(), capacity_); + tail_ = (tail_ + advance) % buffer_.size(); +} + +WebSocketInflater::InputQueue::InputQueue(size_t capacity) + : capacity_(capacity), head_of_first_buffer_(0), tail_of_last_buffer_(0) {} + +WebSocketInflater::InputQueue::~InputQueue() {} + +std::pair<char*, size_t> WebSocketInflater::InputQueue::Top() { + DCHECK(!IsEmpty()); + if (buffers_.size() == 1) { + return std::make_pair(&buffers_.front()->data()[head_of_first_buffer_], + tail_of_last_buffer_ - head_of_first_buffer_); + } + return std::make_pair(&buffers_.front()->data()[head_of_first_buffer_], + capacity_ - head_of_first_buffer_); +} + +void WebSocketInflater::InputQueue::Push(const char* data, size_t size) { + if (!size) + return; + + size_t num_copied_bytes = 0; + if (!IsEmpty()) + num_copied_bytes += PushToLastBuffer(data, size); + + while (num_copied_bytes < size) { + DCHECK(IsEmpty() || tail_of_last_buffer_ == capacity_); + + buffers_.push_back(new IOBufferWithSize(capacity_)); + tail_of_last_buffer_ = 0; + num_copied_bytes += + PushToLastBuffer(&data[num_copied_bytes], size - num_copied_bytes); + } +} + +void WebSocketInflater::InputQueue::Consume(size_t size) { + DCHECK(!IsEmpty()); + DCHECK_LE(size + head_of_first_buffer_, capacity_); + + head_of_first_buffer_ += size; + if (head_of_first_buffer_ == capacity_) { + buffers_.pop_front(); + head_of_first_buffer_ = 0; + } + if (buffers_.size() == 1 && head_of_first_buffer_ == tail_of_last_buffer_) { + buffers_.pop_front(); + head_of_first_buffer_ = 0; + tail_of_last_buffer_ = 0; + } +} + +size_t WebSocketInflater::InputQueue::PushToLastBuffer(const char* data, + size_t size) { + DCHECK(!IsEmpty()); + size_t num_bytes_to_copy = std::min(size, capacity_ - tail_of_last_buffer_); + if (!num_bytes_to_copy) + return 0; + IOBufferWithSize* buffer = buffers_.back().get(); + memcpy(&buffer->data()[tail_of_last_buffer_], data, num_bytes_to_copy); + tail_of_last_buffer_ += num_bytes_to_copy; + return num_bytes_to_copy; +} + +} // namespace net diff --git a/net/websockets/websocket_inflater.h b/net/websockets/websocket_inflater.h new file mode 100644 index 0000000..e57317f --- /dev/null +++ b/net/websockets/websocket_inflater.h @@ -0,0 +1,130 @@ +// 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_INFLATER_H_ +#define NET_WEBSOCKETS_WEBSOCKET_INFLATER_H_ + +#include <deque> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/net_export.h" + +extern "C" struct z_stream_s; + +namespace net { + +class IOBufferWithSize; + +// WebSocketInflater uncompresses data compressed by DEFLATE algorithm. +class NET_EXPORT_PRIVATE WebSocketInflater { + public: + WebSocketInflater(); + // |input_queue_capacity| is a capacity for each contiguous block in the + // input queue. The input queue can grow without limit. + WebSocketInflater(size_t input_queue_capacity, size_t output_buffer_capacity); + ~WebSocketInflater(); + + // Returns true if there is no error. + // |window_bits| must be between 8 and 15 (both inclusive). + // This function must be called exactly once before calling any of the + // following functions. + bool Initialize(int window_bits); + + // Adds bytes to |stream_|. + // Returns true if there is no error. + // If the size of the output data reaches the capacity of the output buffer, + // the following input data will be "choked", i.e. stored in the input queue, + // staying compressed. + bool AddBytes(const char* data, size_t size); + + // Flushes the input. + // Returns true if there is no error. + bool Finish(); + + // Returns up to |size| bytes of the decompressed output. + // Returns null if there is an inflation error. + // The returned bytes will be dropped from the current output and never be + // returned again. + // If some input data is choked, calling this function may restart the + // inflation process. + // This means that even if you call |Finish()| and call |GetOutput()| with + // size = |CurrentOutputSize()|, the inflater may have some remaining data. + // To confirm the inflater emptiness, you should check whether + // |CurrentOutputSize()| is zero. + scoped_refptr<IOBufferWithSize> GetOutput(size_t size); + + // Returns the size of the current inflated output. + size_t CurrentOutputSize() const { return output_buffer_.Size(); } + + static const size_t kDefaultBufferCapacity = 512; + static const size_t kDefaultInputIOBufferCapacity = 512; + + private: + // Ring buffer with fixed capacity. + class OutputBuffer { + public: + explicit OutputBuffer(size_t capacity); + ~OutputBuffer(); + + size_t Size() const; + // Returns (tail pointer, availabe size). + // A user can push data to the queue by writing the data to + // the area returned by this function and calling AdvanceTail. + std::pair<char*, size_t> GetTail(); + void Read(char* dest, size_t size); + void AdvanceTail(size_t advance); + + private: + void AdvanceHead(size_t advance); + + const size_t capacity_; + std::vector<char> buffer_; + size_t head_; + size_t tail_; + }; + + class InputQueue { + public: + // |capacity| is used for the capacity of each IOBuffer in this queue. + // this queue itself can grow without limit. + explicit InputQueue(size_t capacity); + ~InputQueue(); + + // Returns (data pointer, size), the first component of unconsumed data. + // The type of data pointer is non-const because |inflate| function + // requires so. + std::pair<char*, size_t> Top(); + bool IsEmpty() const { return buffers_.empty(); } + void Push(const char* data, size_t size); + // Consumes the topmost |size| bytes. + // |size| must be less than or equal to the first buffer size. + void Consume(size_t size); + + private: + size_t PushToLastBuffer(const char* data, size_t size); + + const size_t capacity_; + size_t head_of_first_buffer_; + size_t tail_of_last_buffer_; + std::deque<scoped_refptr<IOBufferWithSize> > buffers_; + }; + + int InflateWithFlush(const char* next_in, size_t avail_in); + int Inflate(const char* next_in, size_t avail_in, int flush); + int InflateChokedInput(); + + scoped_ptr<z_stream_s> stream_; + InputQueue input_queue_; + OutputBuffer output_buffer_; + + DISALLOW_COPY_AND_ASSIGN(WebSocketInflater); +}; + +} // namespace net + +#endif // NET_WEBSOCKETS_WEBSOCKET_INFLATER_H_ diff --git a/net/websockets/websocket_inflater_test.cc b/net/websockets/websocket_inflater_test.cc new file mode 100644 index 0000000..722e29e8 --- /dev/null +++ b/net/websockets/websocket_inflater_test.cc @@ -0,0 +1,245 @@ +// 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_inflater.h" + +#include <stdint.h> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "net/base/io_buffer.h" +#include "net/websockets/websocket_deflater.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +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)); + + EXPECT_EQ(0u, inflater.CurrentOutputSize()); +} + +TEST(WebSocketInflaterTest, InflateHelloTakeOverContext) { + WebSocketInflater inflater; + ASSERT_TRUE(inflater.Initialize(15)); + scoped_refptr<IOBufferWithSize> actual1, actual2; + + ASSERT_TRUE(inflater.AddBytes("\xf2\x48\xcd\xc9\xc9\x07\x00", 7)); + ASSERT_TRUE(inflater.Finish()); + actual1 = inflater.GetOutput(inflater.CurrentOutputSize()); + ASSERT_TRUE(actual1); + EXPECT_EQ("Hello", ToString(actual1.get())); + EXPECT_EQ(0u, inflater.CurrentOutputSize()); + + ASSERT_TRUE(inflater.AddBytes("\xf2\x00\x11\x00\x00", 5)); + ASSERT_TRUE(inflater.Finish()); + actual2 = inflater.GetOutput(inflater.CurrentOutputSize()); + ASSERT_TRUE(actual2); + EXPECT_EQ("Hello", ToString(actual2.get())); + EXPECT_EQ(0u, inflater.CurrentOutputSize()); +} + +TEST(WebSocketInflaterTest, InflateHelloSmallCapacity) { + WebSocketInflater inflater(1, 1); + ASSERT_TRUE(inflater.Initialize(15)); + std::string actual; + + ASSERT_TRUE(inflater.AddBytes("\xf2\x48\xcd\xc9\xc9\x07\x00", 7)); + ASSERT_TRUE(inflater.Finish()); + for (size_t i = 0; i < 5; ++i) { + ASSERT_EQ(1u, inflater.CurrentOutputSize()); + scoped_refptr<IOBufferWithSize> buffer = inflater.GetOutput(1); + ASSERT_TRUE(buffer); + ASSERT_EQ(1, buffer->size()); + actual += ToString(buffer.get()); + } + EXPECT_EQ("Hello", actual); + EXPECT_EQ(0u, inflater.CurrentOutputSize()); +} + +TEST(WebSocketInflaterTest, InflateHelloSmallCapacityGetTotalOutput) { + WebSocketInflater inflater(1, 1); + ASSERT_TRUE(inflater.Initialize(15)); + scoped_refptr<IOBufferWithSize> actual; + + ASSERT_TRUE(inflater.AddBytes("\xf2\x48\xcd\xc9\xc9\x07\x00", 7)); + ASSERT_TRUE(inflater.Finish()); + ASSERT_EQ(1u, inflater.CurrentOutputSize()); + actual = inflater.GetOutput(1024); + EXPECT_EQ("Hello", ToString(actual)); + EXPECT_EQ(0u, inflater.CurrentOutputSize()); +} + +TEST(WebSocketInflaterTest, InflateInvalidData) { + WebSocketInflater inflater; + ASSERT_TRUE(inflater.Initialize(15)); + EXPECT_FALSE(inflater.AddBytes("\xf2\x48\xcd\xc9INVALID DATA", 16)); +} + +TEST(WebSocketInflaterTest, ChokedInvalidData) { + WebSocketInflater inflater(1, 1); + ASSERT_TRUE(inflater.Initialize(15)); + + EXPECT_TRUE(inflater.AddBytes("\xf2\x48\xcd\xc9INVALID DATA", 16)); + EXPECT_TRUE(inflater.Finish()); + EXPECT_EQ(1u, inflater.CurrentOutputSize()); + EXPECT_FALSE(inflater.GetOutput(1024)); +} + +TEST(WebSocketInflaterTest, MultipleAddBytesCalls) { + WebSocketInflater inflater; + ASSERT_TRUE(inflater.Initialize(15)); + std::string input("\xf2\x48\xcd\xc9\xc9\x07\x00", 7); + scoped_refptr<IOBufferWithSize> actual; + + for (size_t i = 0; i < input.size(); ++i) { + ASSERT_TRUE(inflater.AddBytes(&input[i], 1)); + } + ASSERT_TRUE(inflater.Finish()); + actual = inflater.GetOutput(5); + ASSERT_TRUE(actual); + EXPECT_EQ("Hello", ToString(actual.get())); +} + +TEST(WebSocketInflaterTest, Reset) { + WebSocketInflater inflater; + ASSERT_TRUE(inflater.Initialize(15)); + scoped_refptr<IOBufferWithSize> actual1, actual2; + + ASSERT_TRUE(inflater.AddBytes("\xf2\x48\xcd\xc9\xc9\x07\x00", 7)); + ASSERT_TRUE(inflater.Finish()); + actual1 = inflater.GetOutput(inflater.CurrentOutputSize()); + ASSERT_TRUE(actual1); + EXPECT_EQ("Hello", ToString(actual1.get())); + EXPECT_EQ(0u, inflater.CurrentOutputSize()); + + // Reset the stream with a block [BFINAL = 1, BTYPE = 00, LEN = 0] + ASSERT_TRUE(inflater.AddBytes("\x01", 1)); + ASSERT_TRUE(inflater.Finish()); + ASSERT_EQ(0u, inflater.CurrentOutputSize()); + + ASSERT_TRUE(inflater.AddBytes("\xf2\x48\xcd\xc9\xc9\x07\x00", 7)); + ASSERT_TRUE(inflater.Finish()); + actual2 = inflater.GetOutput(inflater.CurrentOutputSize()); + ASSERT_TRUE(actual2); + EXPECT_EQ("Hello", ToString(actual2.get())); + EXPECT_EQ(0u, inflater.CurrentOutputSize()); +} + +TEST(WebSocketInflaterTest, ResetAndLostContext) { + WebSocketInflater inflater; + scoped_refptr<IOBufferWithSize> actual1, actual2; + ASSERT_TRUE(inflater.Initialize(15)); + + ASSERT_TRUE(inflater.AddBytes("\xf2\x48\xcd\xc9\xc9\x07\x00", 7)); + ASSERT_TRUE(inflater.Finish()); + actual1 = inflater.GetOutput(inflater.CurrentOutputSize()); + ASSERT_TRUE(actual1); + EXPECT_EQ("Hello", ToString(actual1.get())); + EXPECT_EQ(0u, inflater.CurrentOutputSize()); + + // Reset the stream with a block [BFINAL = 1, BTYPE = 00, LEN = 0] + ASSERT_TRUE(inflater.AddBytes("\x01", 1)); + ASSERT_TRUE(inflater.Finish()); + ASSERT_EQ(0u, inflater.CurrentOutputSize()); + + // The context is already reset. + ASSERT_FALSE(inflater.AddBytes("\xf2\x00\x11\x00\x00", 5)); +} + +TEST(WebSocketInflaterTest, CallAddBytesAndFinishWithoutGetOutput) { + WebSocketInflater inflater; + scoped_refptr<IOBufferWithSize> actual1, actual2; + ASSERT_TRUE(inflater.Initialize(15)); + + ASSERT_TRUE(inflater.AddBytes("\xf2\x48\xcd\xc9\xc9\x07\x00", 7)); + ASSERT_TRUE(inflater.Finish()); + EXPECT_EQ(5u, inflater.CurrentOutputSize()); + + // This is a test for detecting memory leaks with valgrind. +} + +TEST(WebSocketInflaterTest, CallAddBytesAndFinishWithoutGetOutputChoked) { + WebSocketInflater inflater(1, 1); + scoped_refptr<IOBufferWithSize> actual1, actual2; + ASSERT_TRUE(inflater.Initialize(15)); + + ASSERT_TRUE(inflater.AddBytes("\xf2\x48\xcd\xc9\xc9\x07\x00", 7)); + ASSERT_TRUE(inflater.Finish()); + EXPECT_EQ(1u, inflater.CurrentOutputSize()); + + // This is a test for detecting memory leaks with valgrind. +} + +TEST(WebSocketInflaterTest, LargeRandomDeflateInflate) { + const size_t size = 64 * 1024; + LinearCongruentialGenerator generator(133); + std::vector<char> input; + std::vector<char> output; + scoped_refptr<IOBufferWithSize> compressed; + + WebSocketDeflater deflater(WebSocketDeflater::TAKE_OVER_CONTEXT); + ASSERT_TRUE(deflater.Initialize(8)); + WebSocketInflater inflater(256, 256); + ASSERT_TRUE(inflater.Initialize(8)); + + for (size_t i = 0; i < size; ++i) + input.push_back(static_cast<char>(generator.Generate())); + + ASSERT_TRUE(deflater.AddBytes(&input[0], input.size())); + ASSERT_TRUE(deflater.Finish()); + + compressed = deflater.GetOutput(deflater.CurrentOutputSize()); + + ASSERT_TRUE(compressed); + ASSERT_EQ(0u, deflater.CurrentOutputSize()); + + ASSERT_TRUE(inflater.AddBytes(compressed->data(), compressed->size())); + ASSERT_TRUE(inflater.Finish()); + + while (inflater.CurrentOutputSize() > 0) { + scoped_refptr<IOBufferWithSize> uncompressed = + inflater.GetOutput(inflater.CurrentOutputSize()); + ASSERT_TRUE(uncompressed); + output.insert(output.end(), + uncompressed->data(), + uncompressed->data() + uncompressed->size()); + } + + EXPECT_EQ(output, input); +} + +} // unnamed namespace + +} // namespace net |