| Index: net/filter/sdch_stream_source_unittest.cc | 
| diff --git a/net/filter/sdch_stream_source_unittest.cc b/net/filter/sdch_stream_source_unittest.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..26c8d118b56757fe43a77e69f260064f6ab027fc | 
| --- /dev/null | 
| +++ b/net/filter/sdch_stream_source_unittest.cc | 
| @@ -0,0 +1,515 @@ | 
| +// Copyright 2016 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 <string> | 
| + | 
| +#include "base/bind.h" | 
| +#include "base/bit_cast.h" | 
| +#include "base/callback.h" | 
| +#include "base/memory/ptr_util.h" | 
| +#include "net/base/io_buffer.h" | 
| +#include "net/base/test_completion_callback.h" | 
| +#include "net/filter/gzip_stream_source.h" | 
| +#include "net/filter/mock_stream_source.h" | 
| +#include "net/filter/sdch_stream_source.h" | 
| +#include "testing/gtest/include/gtest/gtest.h" | 
| +#include "third_party/zlib/zlib.h" | 
| + | 
| +namespace net { | 
| + | 
| +namespace { | 
| + | 
| +const size_t kBufferSize = 4096; | 
| +const char* kDictionaryError = "dictionary error"; | 
| +const char* kDecodingError = "decoding error"; | 
| +const size_t kSmallBufferSize = 1; | 
| + | 
| +// Provide sample data and compression results with a sample VCDIFF dictionary. | 
| +// Note an SDCH dictionary has extra meta-data before the VCDIFF dictionary. | 
| +static const char kTestVcdiffDictionary[] = | 
| +    "DictionaryFor" | 
| +    "SdchCompression1SdchCompression2SdchCompression3SdchCompression\n"; | 
| + | 
| +// Pre-compression test data. Note that we pad with a lot of highly gzip | 
| +// compressible content to help to exercise the chaining pipeline. That is why | 
| +// there are a PILE of zeros at the start and end. | 
| +static const char kTestData[] = | 
| +    "0000000000000000000000000000000000000000000000" | 
| +    "0000000000000000000000000000TestData " | 
| +    "SdchCompression1SdchCompression2SdchCompression3SdchCompression" | 
| +    "00000000000000000000000000000000000000000000000000000000000000000000000000" | 
| +    "000000000000000000000000000000000000000\n"; | 
| +static const char kSdchCompressedTestData[] = | 
| +    "\326\303\304\0\0\001M\0\201S\202\004\0\201E\006\001" | 
| +    "00000000000000000000000000000000000000000000000000000000000000000000000000" | 
| +    "TestData 00000000000000000000000000000000000000000000000000000000000000000" | 
| +    "000000000000000000000000000000000000000000000000\n\001S\023\077\001r\r"; | 
| + | 
| +// Helper function to perform gzip compression of data. | 
| +static std::string gzip_compress(const std::string& input, | 
| +                                 size_t input_size, | 
| +                                 size_t* output_size) { | 
| +  z_stream zlib_stream; | 
| +  memset(&zlib_stream, 0, sizeof(zlib_stream)); | 
| +  int code; | 
| + | 
| +  // Initialize zlib | 
| +  code = | 
| +      deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, | 
| +                   8,  // DEF_MEM_LEVEL | 
| +                   Z_DEFAULT_STRATEGY); | 
| + | 
| +  CHECK_EQ(Z_OK, code); | 
| + | 
| +  // Fill in zlib control block | 
| +  zlib_stream.next_in = bit_cast<Bytef*>(input.data()); | 
| +  zlib_stream.avail_in = input_size; | 
| + | 
| +  // Assume we can compress into similar buffer (add 100 bytes to be sure). | 
| +  size_t gzip_compressed_length = zlib_stream.avail_in + 100; | 
| +  std::unique_ptr<char[]> gzip_compressed(new char[gzip_compressed_length]); | 
| +  zlib_stream.next_out = bit_cast<Bytef*>(gzip_compressed.get()); | 
| +  zlib_stream.avail_out = gzip_compressed_length; | 
| + | 
| +  // The GZIP header (see RFC 1952): | 
| +  //   +---+---+---+---+---+---+---+---+---+---+ | 
| +  //   |ID1|ID2|CM |FLG|     MTIME     |XFL|OS | | 
| +  //   +---+---+---+---+---+---+---+---+---+---+ | 
| +  //     ID1     \037 | 
| +  //     ID2     \213 | 
| +  //     CM      \010 (compression method == DEFLATE) | 
| +  //     FLG     \000 (special flags that we do not support) | 
| +  //     MTIME   Unix format modification time (0 means not available) | 
| +  //     XFL     2-4? DEFLATE flags | 
| +  //     OS      ???? Operating system indicator (255 means unknown) | 
| +  // | 
| +  // Header value we generate: | 
| +  const char kGZipHeader[] = {'\037', '\213', '\010', '\000', '\000', | 
| +                              '\000', '\000', '\000', '\002', '\377'}; | 
| +  CHECK_GT(zlib_stream.avail_out, sizeof(kGZipHeader)); | 
| +  memcpy(zlib_stream.next_out, kGZipHeader, sizeof(kGZipHeader)); | 
| +  zlib_stream.next_out += sizeof(kGZipHeader); | 
| +  zlib_stream.avail_out -= sizeof(kGZipHeader); | 
| + | 
| +  // Do deflate | 
| +  code = deflate(&zlib_stream, Z_FINISH); | 
| +  gzip_compressed_length -= zlib_stream.avail_out; | 
| +  std::string compressed(gzip_compressed.get(), gzip_compressed_length); | 
| +  deflateEnd(&zlib_stream); | 
| +  *output_size = gzip_compressed_length; | 
| +  return compressed; | 
| +} | 
| + | 
| +}  // namespace | 
| + | 
| +class SdchStreamSourceTest : public ::testing::Test, | 
| +                             public SdchStreamSource::Delegate { | 
| + public: | 
| +  SdchStreamSourceTest() | 
| +      : dictionary_error_handled_(false), decoding_error_handled_(false) {} | 
| + | 
| +  void SetUp() override { | 
| +    ::testing::Test::SetUp(); | 
| + | 
| +    out_buffer_ = new IOBufferWithSize(kBufferSize); | 
| + | 
| +    std::unique_ptr<MockStreamSource> source(new MockStreamSource); | 
| +    source_ = source.get(); | 
| +    sdch_source_.reset(new SdchStreamSource(std::move(source), this)); | 
| +  } | 
| + | 
| +  IOBuffer* out_buffer() { return out_buffer_.get(); } | 
| +  char* out_data() { return out_buffer_->data(); } | 
| +  size_t out_buffer_size() { return out_buffer_->size(); } | 
| + | 
| +  MockStreamSource* mock_source() { return source_; } | 
| +  SdchStreamSource* sdch_source() { return sdch_source_.get(); } | 
| + | 
| +  bool dictionary_error_handled() { return dictionary_error_handled_; } | 
| +  bool decoding_error_handled() { return decoding_error_handled_; } | 
| +  std::string last_get_dictionary_id() { return last_get_dictionary_id_; } | 
| + | 
| +  int ReadStream(const TestCompletionCallback& callback) { | 
| +    return sdch_source()->Read(out_buffer(), out_buffer_size(), | 
| +                               callback.callback()); | 
| +  } | 
| + | 
| +  void SetTestDictionary(const std::string& dictionary_id, | 
| +                         const std::string& dictionary_text) { | 
| +    test_dictionary_id_ = dictionary_id; | 
| +    test_dictionary_text_ = dictionary_text; | 
| +  } | 
| + | 
| +  void AppendDictionaryIdTo(std::string* resp, std::string* server_id) { | 
| +    std::string client_id; | 
| +    SdchManager::GenerateHash(kTestVcdiffDictionary, &client_id, server_id); | 
| +    SetTestDictionary(*server_id, kTestVcdiffDictionary); | 
| +    std::string response(server_id->data(), server_id->size()); | 
| +    response.append("\0"); | 
| +    resp->append(response.data(), server_id->size() + 1); | 
| +  } | 
| + | 
| +  // SdchStreamSource::Delegate implementation. | 
| +  bool OnDictionaryError(SdchStreamSource* source) override { | 
| +    dictionary_error_handled_ = true; | 
| +    sdch_source()->ReplaceOutput(kDictionaryError, strlen(kDictionaryError)); | 
| +    return true; | 
| +  } | 
| + | 
| +  bool OnDecodingError(SdchStreamSource* source) override { | 
| +    decoding_error_handled_ = true; | 
| +    sdch_source()->ReplaceOutput(kDecodingError, strlen(kDictionaryError)); | 
| +    return true; | 
| +  } | 
| + | 
| +  bool OnGetDictionary(const std::string& server_id, | 
| +                       const std::string** text) override { | 
| +    last_get_dictionary_id_ = server_id; | 
| +    if (server_id == test_dictionary_id_) { | 
| +      *text = &test_dictionary_text_; | 
| +      return true; | 
| +    } | 
| +    return false; | 
| +  } | 
| + | 
| + protected: | 
| +  scoped_refptr<IOBufferWithSize> out_buffer_; | 
| + | 
| + private: | 
| +  // Owned by sdch_source_. | 
| +  MockStreamSource* source_; | 
| +  std::unique_ptr<SdchStreamSource> sdch_source_; | 
| + | 
| +  std::string test_dictionary_id_; | 
| +  std::string test_dictionary_text_; | 
| + | 
| +  std::string last_get_dictionary_id_; | 
| +  bool dictionary_error_handled_; | 
| +  bool decoding_error_handled_; | 
| +}; | 
| + | 
| +TEST_F(SdchStreamSourceTest, EmptyStream) { | 
| +  mock_source()->AddReadResult("", 0, OK, true); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadStream(callback); | 
| +  EXPECT_EQ(OK, bytes_read); | 
| +  EXPECT_EQ("SDCH", sdch_source()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +// Ensure that GetDictionary() is not called at all if the SDCH dictionary ID is | 
| +// malformed. | 
| +TEST_F(SdchStreamSourceTest, BogusDictionaryId) { | 
| +  char id[] = {0x1f, '0', '0', '0', '0', '0', '0', '0', 0x0}; | 
| +  mock_source()->AddReadResult(id, sizeof(id), OK, true); | 
| +  SetTestDictionary(id, "..."); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadStream(callback); | 
| + | 
| +  EXPECT_TRUE(dictionary_error_handled()); | 
| +  EXPECT_EQ("", last_get_dictionary_id()); | 
| +  EXPECT_EQ(static_cast<int>(strlen(kDictionaryError)), bytes_read); | 
| +  EXPECT_EQ(0, memcmp(kDictionaryError, out_data(), bytes_read)); | 
| +  EXPECT_EQ("SDCH", sdch_source()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +// Ensure that the stream's dictionary error handler is called if GetDictionary | 
| +// returns no dictionary. | 
| +TEST_F(SdchStreamSourceTest, NoDictionaryError) { | 
| +  char id[] = "00000000"; | 
| +  mock_source()->AddReadResult(id, sizeof(id), OK, true); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadStream(callback); | 
| +  EXPECT_EQ(static_cast<int>(strlen(kDictionaryError)), bytes_read); | 
| +  EXPECT_TRUE(dictionary_error_handled()); | 
| +  EXPECT_EQ(id, last_get_dictionary_id()); | 
| +  EXPECT_EQ("SDCH", sdch_source()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(SdchStreamSourceTest, DictionaryLoaded) { | 
| +  std::string response; | 
| +  std::string server_id; | 
| +  AppendDictionaryIdTo(&response, &server_id); | 
| +  mock_source()->AddReadResult(response.data(), response.size(), OK, true); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadStream(callback); | 
| +  // Decoded response should be empty. | 
| +  EXPECT_EQ(0, bytes_read); | 
| +  EXPECT_FALSE(dictionary_error_handled()); | 
| +  EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +  EXPECT_EQ("SDCH", sdch_source()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(SdchStreamSourceTest, DecompressOneBlockSync) { | 
| +  std::string response; | 
| +  std::string server_id; | 
| +  AppendDictionaryIdTo(&response, &server_id); | 
| +  mock_source()->AddReadResult(response.data(), response.size(), OK, true); | 
| +  mock_source()->AddReadResult(kSdchCompressedTestData, | 
| +                               sizeof(kSdchCompressedTestData) - 1, OK, true); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadStream(callback); | 
| + | 
| +  EXPECT_FALSE(dictionary_error_handled()); | 
| +  EXPECT_FALSE(decoding_error_handled()); | 
| +  EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +  EXPECT_EQ(static_cast<int>(sizeof(kTestData) - 1), bytes_read); | 
| +  EXPECT_EQ(0, memcmp(kTestData, out_data(), bytes_read)); | 
| +  EXPECT_EQ("SDCH", sdch_source()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(SdchStreamSourceTest, DecompressOneBlockAsync) { | 
| +  std::string response; | 
| +  std::string server_id; | 
| +  AppendDictionaryIdTo(&response, &server_id); | 
| +  mock_source()->AddReadResult(response.data(), response.size(), OK, false); | 
| +  mock_source()->AddReadResult(kSdchCompressedTestData, | 
| +                               sizeof(kSdchCompressedTestData) - 1, OK, false); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadStream(callback); | 
| +  EXPECT_EQ(ERR_IO_PENDING, bytes_read); | 
| +  mock_source()->CompleteNextRead(); | 
| +  mock_source()->CompleteNextRead(); | 
| +  int rv = callback.WaitForResult(); | 
| +  EXPECT_FALSE(dictionary_error_handled()); | 
| +  EXPECT_FALSE(decoding_error_handled()); | 
| +  EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +  EXPECT_EQ(static_cast<int>(sizeof(kTestData) - 1), rv); | 
| +  EXPECT_EQ(0, memcmp(kTestData, out_data(), rv)); | 
| +  EXPECT_EQ("SDCH", sdch_source()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(SdchStreamSourceTest, DecompressWithSmallBufferSync) { | 
| +  out_buffer_ = new IOBufferWithSize(kSmallBufferSize); | 
| +  std::string response; | 
| +  std::string server_id; | 
| +  AppendDictionaryIdTo(&response, &server_id); | 
| +  mock_source()->AddReadResult(response.data(), response.size(), OK, true); | 
| +  mock_source()->AddReadResult(kSdchCompressedTestData, | 
| +                               sizeof(kSdchCompressedTestData) - 1, OK, true); | 
| +  char buffer[sizeof(kTestData)]; | 
| +  size_t total_bytes_read = 0; | 
| +  int bytes_read = 0; | 
| +  TestCompletionCallback callback; | 
| +  do { | 
| +    bytes_read = ReadStream(callback); | 
| +    EXPECT_GE(kSmallBufferSize, static_cast<size_t>(bytes_read)); | 
| +    memcpy(buffer + total_bytes_read, out_data(), bytes_read); | 
| +    total_bytes_read += bytes_read; | 
| +  } while (bytes_read > 0); | 
| + | 
| +  EXPECT_FALSE(dictionary_error_handled()); | 
| +  EXPECT_FALSE(decoding_error_handled()); | 
| +  EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +  EXPECT_EQ(sizeof(kTestData) - 1, total_bytes_read); | 
| +  EXPECT_EQ(0, memcmp(kTestData, buffer, total_bytes_read)); | 
| +  EXPECT_EQ("SDCH", sdch_source()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(SdchStreamSourceTest, DecompressWithSmallBufferAsync) { | 
| +  out_buffer_ = new IOBufferWithSize(kSmallBufferSize); | 
| +  std::string response; | 
| +  std::string server_id; | 
| +  AppendDictionaryIdTo(&response, &server_id); | 
| +  mock_source()->AddReadResult(response.data(), response.size(), OK, false); | 
| +  mock_source()->AddReadResult(kSdchCompressedTestData, | 
| +                               sizeof(kSdchCompressedTestData) - 1, OK, false); | 
| +  char buffer[sizeof(kTestData)]; | 
| +  size_t total_bytes_read = 0; | 
| +  int bytes_read = 0; | 
| +  TestCompletionCallback callback; | 
| +  do { | 
| +    bytes_read = ReadStream(callback); | 
| +    if (bytes_read == ERR_IO_PENDING) { | 
| +      mock_source()->CompleteNextRead(); | 
| +      mock_source()->CompleteNextRead(); | 
| +      bytes_read = callback.WaitForResult(); | 
| +    } | 
| +    EXPECT_GE(kSmallBufferSize, static_cast<size_t>(bytes_read)); | 
| +    memcpy(buffer + total_bytes_read, out_data(), bytes_read); | 
| +    total_bytes_read += bytes_read; | 
| +  } while (bytes_read > 0); | 
| +  EXPECT_FALSE(dictionary_error_handled()); | 
| +  EXPECT_FALSE(decoding_error_handled()); | 
| +  EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +  EXPECT_EQ(sizeof(kTestData) - 1, total_bytes_read); | 
| +  EXPECT_EQ(0, memcmp(kTestData, buffer, total_bytes_read)); | 
| +  EXPECT_EQ("SDCH", sdch_source()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(SdchStreamSourceTest, DecompressTwoBlocksSync) { | 
| +  std::string response; | 
| +  std::string server_id; | 
| +  AppendDictionaryIdTo(&response, &server_id); | 
| +  mock_source()->AddReadResult(response.data(), response.size(), OK, true); | 
| +  mock_source()->AddReadResult(kSdchCompressedTestData, 32, OK, true); | 
| +  mock_source()->AddReadResult(kSdchCompressedTestData + 32, | 
| +                               sizeof(kSdchCompressedTestData) - 1 - 32, OK, | 
| +                               true); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadStream(callback); | 
| +  EXPECT_FALSE(dictionary_error_handled()); | 
| +  EXPECT_FALSE(decoding_error_handled()); | 
| +  EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +  EXPECT_EQ(static_cast<int>(sizeof(kTestData) - 1), bytes_read); | 
| +  EXPECT_EQ(0, memcmp(kTestData, out_data(), bytes_read)); | 
| +  EXPECT_EQ("SDCH", sdch_source()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +// Test that filters can be cascaded (chained) so that the output of one filter | 
| +// is processed by the next one. This is most critical for SDCH, which is | 
| +// routinely followed by gzip (during encoding). The filter we'll test for will | 
| +// do the gzip decoding first, and then decode the SDCH content. | 
| +TEST_F(SdchStreamSourceTest, FilterChaining) { | 
| +  { | 
| +    std::string sdch_response; | 
| +    std::string server_id; | 
| +    AppendDictionaryIdTo(&sdch_response, &server_id); | 
| +    sdch_response.append(kSdchCompressedTestData, | 
| +                         sizeof(kSdchCompressedTestData) - 1); | 
| + | 
| +    size_t expected_length = | 
| +        server_id.length() + sizeof(kSdchCompressedTestData); | 
| +    size_t gzip_length; | 
| +    std::string gzip_compressed_sdch = | 
| +        gzip_compress(sdch_response, expected_length, &gzip_length); | 
| +    std::unique_ptr<MockStreamSource> source(new MockStreamSource); | 
| +    source->AddReadResult(gzip_compressed_sdch.data(), gzip_length, OK, true); | 
| +    std::unique_ptr<GzipStreamSource> gzip_source = GzipStreamSource::Create( | 
| +        std::move(source), GzipStreamSource::GZIP_STREAM_SOURCE_GZIP); | 
| +    std::unique_ptr<SdchStreamSource> sdch_source( | 
| +        new SdchStreamSource(std::move(gzip_source), this)); | 
| +    TestCompletionCallback callback; | 
| +    int bytes_read = | 
| +        sdch_source->Read(out_buffer(), out_buffer_size(), callback.callback()); | 
| + | 
| +    EXPECT_FALSE(dictionary_error_handled()); | 
| +    EXPECT_FALSE(decoding_error_handled()); | 
| +    EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +    EXPECT_EQ(static_cast<int>(sizeof(kTestData) - 1), bytes_read); | 
| +    EXPECT_EQ(0, memcmp(kTestData, out_data(), bytes_read)); | 
| +    EXPECT_EQ("GZIP,SDCH", sdch_source->OrderedTypeStringList()); | 
| +  } | 
| + | 
| +  { | 
| +    // Try a small buffer | 
| +    out_buffer_ = new IOBufferWithSize(kSmallBufferSize); | 
| +    std::string sdch_response; | 
| +    std::string server_id; | 
| +    AppendDictionaryIdTo(&sdch_response, &server_id); | 
| +    sdch_response.append(kSdchCompressedTestData, | 
| +                         sizeof(kSdchCompressedTestData) - 1); | 
| + | 
| +    size_t expected_length = | 
| +        server_id.length() + sizeof(kSdchCompressedTestData); | 
| +    size_t gzip_length; | 
| +    std::string gzip_compressed_sdch = | 
| +        gzip_compress(sdch_response, expected_length, &gzip_length); | 
| +    std::unique_ptr<MockStreamSource> source(new MockStreamSource); | 
| +    source->AddReadResult(gzip_compressed_sdch.data(), gzip_length, OK, true); | 
| +    std::unique_ptr<GzipStreamSource> gzip_source = GzipStreamSource::Create( | 
| +        std::move(source), GzipStreamSource::GZIP_STREAM_SOURCE_GZIP); | 
| +    std::unique_ptr<SdchStreamSource> sdch_source( | 
| +        new SdchStreamSource(std::move(gzip_source), this)); | 
| +    char buffer[sizeof(kTestData)]; | 
| +    size_t total_bytes_read = 0; | 
| +    int bytes_read = 0; | 
| +    TestCompletionCallback callback; | 
| +    do { | 
| +      bytes_read = sdch_source->Read(out_buffer(), out_buffer_size(), | 
| +                                     callback.callback()); | 
| +      EXPECT_GE(kSmallBufferSize, static_cast<size_t>(bytes_read)); | 
| +      memcpy(buffer + total_bytes_read, out_data(), bytes_read); | 
| +      total_bytes_read += bytes_read; | 
| +    } while (bytes_read > 0); | 
| + | 
| +    EXPECT_FALSE(dictionary_error_handled()); | 
| +    EXPECT_FALSE(decoding_error_handled()); | 
| +    EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +    EXPECT_EQ(sizeof(kTestData) - 1, total_bytes_read); | 
| +    EXPECT_EQ(0, memcmp(kTestData, buffer, total_bytes_read)); | 
| +    EXPECT_EQ("GZIP,SDCH", sdch_source->OrderedTypeStringList()); | 
| +  } | 
| +} | 
| + | 
| +TEST_F(SdchStreamSourceTest, FilterChainingAsync) { | 
| +  { | 
| +    std::string sdch_response; | 
| +    std::string server_id; | 
| +    AppendDictionaryIdTo(&sdch_response, &server_id); | 
| +    sdch_response.append(kSdchCompressedTestData, | 
| +                         sizeof(kSdchCompressedTestData) - 1); | 
| + | 
| +    size_t expected_length = | 
| +        server_id.length() + sizeof(kSdchCompressedTestData); | 
| +    size_t gzip_length; | 
| +    std::string gzip_compressed_sdch = | 
| +        gzip_compress(sdch_response, expected_length, &gzip_length); | 
| +    MockStreamSource* source = new MockStreamSource; | 
| +    source->AddReadResult(gzip_compressed_sdch.data(), gzip_length, OK, false); | 
| +    std::unique_ptr<GzipStreamSource> gzip_source = GzipStreamSource::Create( | 
| +        base::WrapUnique(source), GzipStreamSource::GZIP_STREAM_SOURCE_GZIP); | 
| +    std::unique_ptr<SdchStreamSource> sdch_source( | 
| +        new SdchStreamSource(std::move(gzip_source), this)); | 
| +    TestCompletionCallback callback; | 
| +    int bytes_read = | 
| +        sdch_source->Read(out_buffer(), out_buffer_size(), callback.callback()); | 
| +    EXPECT_EQ(ERR_IO_PENDING, bytes_read); | 
| +    source->CompleteNextRead(); | 
| +    bytes_read = callback.WaitForResult(); | 
| + | 
| +    EXPECT_FALSE(dictionary_error_handled()); | 
| +    EXPECT_FALSE(decoding_error_handled()); | 
| +    EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +    EXPECT_EQ(static_cast<int>(sizeof(kTestData) - 1), bytes_read); | 
| +    EXPECT_EQ(0, memcmp(kTestData, out_data(), bytes_read)); | 
| +    EXPECT_EQ("GZIP,SDCH", sdch_source->OrderedTypeStringList()); | 
| +  } | 
| + | 
| +  { | 
| +    // Try a small buffer | 
| +    out_buffer_ = new IOBufferWithSize(kSmallBufferSize); | 
| +    std::string sdch_response; | 
| +    std::string server_id; | 
| +    AppendDictionaryIdTo(&sdch_response, &server_id); | 
| +    sdch_response.append(kSdchCompressedTestData, | 
| +                         sizeof(kSdchCompressedTestData) - 1); | 
| + | 
| +    size_t expected_length = | 
| +        server_id.length() + sizeof(kSdchCompressedTestData); | 
| +    size_t gzip_length; | 
| +    std::string gzip_compressed_sdch = | 
| +        gzip_compress(sdch_response, expected_length, &gzip_length); | 
| +    MockStreamSource* source = new MockStreamSource; | 
| +    source->AddReadResult(gzip_compressed_sdch.data(), gzip_length, OK, false); | 
| +    std::unique_ptr<GzipStreamSource> gzip_source = GzipStreamSource::Create( | 
| +        base::WrapUnique(source), GzipStreamSource::GZIP_STREAM_SOURCE_GZIP); | 
| +    std::unique_ptr<SdchStreamSource> sdch_source( | 
| +        new SdchStreamSource(std::move(gzip_source), this)); | 
| +    char buffer[sizeof(kTestData)]; | 
| +    size_t total_bytes_read = 0; | 
| +    int bytes_read = 0; | 
| +    TestCompletionCallback callback; | 
| +    do { | 
| +      bytes_read = sdch_source->Read(out_buffer(), out_buffer_size(), | 
| +                                     callback.callback()); | 
| +      if (bytes_read == ERR_IO_PENDING) { | 
| +        source->CompleteNextRead(); | 
| +        bytes_read = callback.WaitForResult(); | 
| +      } | 
| + | 
| +      EXPECT_GE(kSmallBufferSize, static_cast<size_t>(bytes_read)); | 
| +      memcpy(buffer + total_bytes_read, out_data(), bytes_read); | 
| +      total_bytes_read += bytes_read; | 
| +    } while (bytes_read > 0); | 
| + | 
| +    EXPECT_FALSE(dictionary_error_handled()); | 
| +    EXPECT_FALSE(decoding_error_handled()); | 
| +    EXPECT_EQ(server_id, last_get_dictionary_id()); | 
| +    EXPECT_EQ(sizeof(kTestData) - 1, total_bytes_read); | 
| +    EXPECT_EQ(0, memcmp(kTestData, buffer, total_bytes_read)); | 
| +    EXPECT_EQ("GZIP,SDCH", sdch_source->OrderedTypeStringList()); | 
| +  } | 
| +} | 
| + | 
| +}  // namespace net | 
|  |