普通文本  |  1397行  |  43.76 KB

// Copyright (c) 2011 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 <algorithm>
#include <iostream>

#include "base/memory/scoped_ptr.h"
#include "net/spdy/spdy_framer.h"
#include "net/spdy/spdy_protocol.h"
#include "net/spdy/spdy_frame_builder.h"
#include "testing/platform_test.h"

namespace spdy {

namespace test {

std::string HexDumpWithMarks(const unsigned char* data, int length,
                             const bool* marks, int mark_length) {
  static const char kHexChars[] = "0123456789ABCDEF";
  static const int kColumns = 4;

  std::string hex;
  for (const unsigned char* row = data; length > 0;
       row += kColumns, length -= kColumns) {
    for (const unsigned char *p = row; p < row + 4; ++p) {
      if (p < row + length) {
        const bool mark =
            (marks && (p - data) < mark_length && marks[p - data]);
        hex += mark ? '*' : ' ';
        hex += kHexChars[(*p & 0xf0) >> 4];
        hex += kHexChars[*p & 0x0f];
        hex += mark ? '*' : ' ';
      } else {
        hex += "    ";
      }
    }
    hex = hex + "  ";

    for (const unsigned char *p = row; p < row + 4 && p < row + length; ++p)
      hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.';

    hex = hex + '\n';
  }
  return hex;
}

void CompareCharArraysWithHexError(
    const std::string& description,
    const unsigned char* actual,
    const int actual_len,
    const unsigned char* expected,
    const int expected_len) {
  const int min_len = actual_len > expected_len ? expected_len : actual_len;
  const int max_len = actual_len > expected_len ? actual_len : expected_len;
  scoped_array<bool> marks(new bool[max_len]);
  bool identical = (actual_len == expected_len);
  for (int i = 0; i < min_len; ++i) {
    if (actual[i] != expected[i]) {
      marks[i] = true;
      identical = false;
    } else {
      marks[i] = false;
    }
  }
  for (int i = min_len; i < max_len; ++i) {
    marks[i] = true;
  }
  if (identical) return;
  ADD_FAILURE()
      << "Description:\n"
      << description
      << "\n\nExpected:\n"
      << HexDumpWithMarks(expected, expected_len, marks.get(), max_len)
      << "\nActual:\n"
      << HexDumpWithMarks(actual, actual_len, marks.get(), max_len);
}

void FramerSetEnableCompressionHelper(SpdyFramer* framer, bool compress) {
  framer->set_enable_compression(compress);
}

class TestSpdyVisitor : public SpdyFramerVisitorInterface  {
 public:
  TestSpdyVisitor()
    : error_count_(0),
      syn_frame_count_(0),
      syn_reply_frame_count_(0),
      headers_frame_count_(0),
      data_bytes_(0),
      fin_frame_count_(0),
      fin_flag_count_(0),
      zero_length_data_frame_count_(0) {
  }

  void OnError(SpdyFramer* f) {
    error_count_++;
  }

  void OnStreamFrameData(SpdyStreamId stream_id,
                         const char* data,
                         size_t len) {
    if (len == 0)
      ++zero_length_data_frame_count_;

    data_bytes_ += len;
    std::cerr << "OnStreamFrameData(" << stream_id << ", \"";
    if (len > 0) {
      for (size_t i = 0 ; i < len; ++i) {
        std::cerr << std::hex << (0xFF & (unsigned int)data[i]) << std::dec;
      }
    }
    std::cerr << "\", " << len << ")\n";
  }

  void OnControl(const SpdyControlFrame* frame) {
    SpdyHeaderBlock headers;
    bool parsed_headers = false;
    switch (frame->type()) {
      case SYN_STREAM:
        parsed_headers = framer_.ParseHeaderBlock(frame, &headers);
        DCHECK(parsed_headers);
        syn_frame_count_++;
        break;
      case SYN_REPLY:
        parsed_headers = framer_.ParseHeaderBlock(frame, &headers);
        DCHECK(parsed_headers);
        syn_reply_frame_count_++;
        break;
      case RST_STREAM:
        fin_frame_count_++;
        break;
      case HEADERS:
        parsed_headers = framer_.ParseHeaderBlock(frame, &headers);
        DCHECK(parsed_headers);
        headers_frame_count_++;
        break;
      default:
        DCHECK(false);  // Error!
    }
    if (frame->flags() & CONTROL_FLAG_FIN)
      ++fin_flag_count_;
  }

  bool OnControlFrameHeaderData(SpdyStreamId stream_id,
                                const char* header_data,
                                size_t len) {
    DCHECK(false);
    return false;
  }

  void OnDataFrameHeader(const SpdyDataFrame* frame) {
    DCHECK(false);
  }

  // Convenience function which runs a framer simulation with particular input.
  void SimulateInFramer(const unsigned char* input, size_t size) {
    framer_.set_enable_compression(false);
    framer_.set_visitor(this);
    size_t input_remaining = size;
    const char* input_ptr = reinterpret_cast<const char*>(input);
    while (input_remaining > 0 &&
           framer_.error_code() == SpdyFramer::SPDY_NO_ERROR) {
      // To make the tests more interesting, we feed random (amd small) chunks
      // into the framer.  This simulates getting strange-sized reads from
      // the socket.
      const size_t kMaxReadSize = 32;
      size_t bytes_read =
          (rand() % std::min(input_remaining, kMaxReadSize)) + 1;
      size_t bytes_processed = framer_.ProcessInput(input_ptr, bytes_read);
      input_remaining -= bytes_processed;
      input_ptr += bytes_processed;
      if (framer_.state() == SpdyFramer::SPDY_DONE)
        framer_.Reset();
    }
  }

  SpdyFramer framer_;
  // Counters from the visitor callbacks.
  int error_count_;
  int syn_frame_count_;
  int syn_reply_frame_count_;
  int headers_frame_count_;
  int data_bytes_;
  int fin_frame_count_;  // The count of RST_STREAM type frames received.
  int fin_flag_count_;  // The count of frames with the FIN flag set.
  int zero_length_data_frame_count_;  // The count of zero-length data frames.
};

}  // namespace test

}  // namespace spdy

using spdy::SpdyControlFlags;
using spdy::SpdyControlFrame;
using spdy::SpdyDataFrame;
using spdy::SpdyFrame;
using spdy::SpdyFrameBuilder;
using spdy::SpdyFramer;
using spdy::SpdyHeaderBlock;
using spdy::SpdySynStreamControlFrame;
using spdy::kControlFlagMask;
using spdy::CONTROL_FLAG_NONE;
using spdy::DATA_FLAG_COMPRESSED;
using spdy::DATA_FLAG_FIN;
using spdy::SYN_STREAM;
using spdy::test::CompareCharArraysWithHexError;
using spdy::test::FramerSetEnableCompressionHelper;
using spdy::test::TestSpdyVisitor;

namespace spdy {

class SpdyFramerTest : public PlatformTest {
 public:
  virtual void TearDown() {}

 protected:
  void CompareFrame(const std::string& description,
                    const SpdyFrame& actual_frame,
                    const unsigned char* expected,
                    const int expected_len) {
    const unsigned char* actual =
        reinterpret_cast<const unsigned char*>(actual_frame.data());
    int actual_len = actual_frame.length() + SpdyFrame::size();
    CompareCharArraysWithHexError(
        description, actual, actual_len, expected, expected_len);
  }
};

// Test that we can encode and decode a SpdyHeaderBlock.
TEST_F(SpdyFramerTest, HeaderBlock) {
  SpdyHeaderBlock headers;
  headers["alpha"] = "beta";
  headers["gamma"] = "charlie";
  SpdyFramer framer;

  // Encode the header block into a SynStream frame.
  scoped_ptr<SpdySynStreamControlFrame> frame(
      framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &headers));
  EXPECT_TRUE(frame.get() != NULL);

  SpdyHeaderBlock new_headers;
  EXPECT_TRUE(framer.ParseHeaderBlock(frame.get(), &new_headers));

  EXPECT_EQ(headers.size(), new_headers.size());
  EXPECT_EQ(headers["alpha"], new_headers["alpha"]);
  EXPECT_EQ(headers["gamma"], new_headers["gamma"]);
}

TEST_F(SpdyFramerTest, OutOfOrderHeaders) {
  SpdyFrameBuilder frame;

  frame.WriteUInt16(kControlFlagMask | 1);
  frame.WriteUInt16(SYN_STREAM);
  frame.WriteUInt32(0);  // Placeholder for the length.
  frame.WriteUInt32(3);  // stream_id
  frame.WriteUInt32(0);  // associated stream id
  frame.WriteUInt16(0);  // Priority.

  frame.WriteUInt16(2);  // Number of headers.
  SpdyHeaderBlock::iterator it;
  frame.WriteString("gamma");
  frame.WriteString("gamma");
  frame.WriteString("alpha");
  frame.WriteString("alpha");
  // write the length
  frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::size());

  SpdyHeaderBlock new_headers;
  scoped_ptr<SpdyFrame> control_frame(frame.take());
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, false);
  EXPECT_TRUE(framer.ParseHeaderBlock(control_frame.get(), &new_headers));
}

TEST_F(SpdyFramerTest, WrongNumberOfHeaders) {
  SpdyFrameBuilder frame1;
  SpdyFrameBuilder frame2;

  // a frame with smaller number of actual headers
  frame1.WriteUInt16(kControlFlagMask | 1);
  frame1.WriteUInt16(SYN_STREAM);
  frame1.WriteUInt32(0);  // Placeholder for the length.
  frame1.WriteUInt32(3);  // stream_id
  frame1.WriteUInt16(0);  // Priority.

  frame1.WriteUInt16(1);  // Wrong number of headers (underflow)
  frame1.WriteString("gamma");
  frame1.WriteString("gamma");
  frame1.WriteString("alpha");
  frame1.WriteString("alpha");
  // write the length
  frame1.WriteUInt32ToOffset(4, frame1.length() - SpdyFrame::size());

  // a frame with larger number of actual headers
  frame2.WriteUInt16(kControlFlagMask | 1);
  frame2.WriteUInt16(SYN_STREAM);
  frame2.WriteUInt32(0);  // Placeholder for the length.
  frame2.WriteUInt32(3);  // stream_id
  frame2.WriteUInt16(0);  // Priority.

  frame2.WriteUInt16(100);  // Wrong number of headers (overflow)
  frame2.WriteString("gamma");
  frame2.WriteString("gamma");
  frame2.WriteString("alpha");
  frame2.WriteString("alpha");
  // write the length
  frame2.WriteUInt32ToOffset(4, frame2.length() - SpdyFrame::size());

  SpdyHeaderBlock new_headers;
  scoped_ptr<SpdyFrame> syn_frame1(frame1.take());
  scoped_ptr<SpdyFrame> syn_frame2(frame2.take());
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, false);
  EXPECT_FALSE(framer.ParseHeaderBlock(syn_frame1.get(), &new_headers));
  EXPECT_FALSE(framer.ParseHeaderBlock(syn_frame2.get(), &new_headers));
}

TEST_F(SpdyFramerTest, DuplicateHeader) {
  SpdyFrameBuilder frame;

  frame.WriteUInt16(kControlFlagMask | 1);
  frame.WriteUInt16(SYN_STREAM);
  frame.WriteUInt32(0);  // Placeholder for the length.
  frame.WriteUInt32(3);  // stream_id
  frame.WriteUInt32(0);  // associated stream id
  frame.WriteUInt16(0);  // Priority.

  frame.WriteUInt16(2);  // Number of headers.
  SpdyHeaderBlock::iterator it;
  frame.WriteString("name");
  frame.WriteString("value1");
  frame.WriteString("name");
  frame.WriteString("value2");
  // write the length
  frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::size());

  SpdyHeaderBlock new_headers;
  scoped_ptr<SpdyFrame> control_frame(frame.take());
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, false);
  // This should fail because duplicate headers are verboten by the spec.
  EXPECT_FALSE(framer.ParseHeaderBlock(control_frame.get(), &new_headers));
}

TEST_F(SpdyFramerTest, MultiValueHeader) {
  SpdyFrameBuilder frame;

  frame.WriteUInt16(kControlFlagMask | 1);
  frame.WriteUInt16(SYN_STREAM);
  frame.WriteUInt32(0);  // Placeholder for the length.
  frame.WriteUInt32(3);  // stream_id
  frame.WriteUInt32(0);  // associated stream id
  frame.WriteUInt16(0);  // Priority.

  frame.WriteUInt16(1);  // Number of headers.
  SpdyHeaderBlock::iterator it;
  frame.WriteString("name");
  std::string value("value1\0value2");
  frame.WriteString(value);
  // write the length
  frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::size());

  SpdyHeaderBlock new_headers;
  scoped_ptr<SpdyFrame> control_frame(frame.take());
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, false);
  EXPECT_TRUE(framer.ParseHeaderBlock(control_frame.get(), &new_headers));
  EXPECT_TRUE(new_headers.find("name") != new_headers.end());
  EXPECT_EQ(value, new_headers.find("name")->second);
}

TEST_F(SpdyFramerTest, ZeroLengthHeader) {
  SpdyHeaderBlock header1;
  SpdyHeaderBlock header2;
  SpdyHeaderBlock header3;

  header1[""] = "value2";
  header2["name3"] = "";
  header3[""] = "";

  SpdyFramer framer;
  SpdyHeaderBlock parsed_headers;

  scoped_ptr<SpdySynStreamControlFrame> frame1(
      framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &header1));
  EXPECT_TRUE(frame1.get() != NULL);
  EXPECT_FALSE(framer.ParseHeaderBlock(frame1.get(), &parsed_headers));

  scoped_ptr<SpdySynStreamControlFrame> frame2(
      framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &header2));
  EXPECT_TRUE(frame2.get() != NULL);
  EXPECT_FALSE(framer.ParseHeaderBlock(frame2.get(), &parsed_headers));

  scoped_ptr<SpdySynStreamControlFrame> frame3(
      framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &header3));
  EXPECT_TRUE(frame3.get() != NULL);
  EXPECT_FALSE(framer.ParseHeaderBlock(frame3.get(), &parsed_headers));
}

TEST_F(SpdyFramerTest, BasicCompression) {
  SpdyHeaderBlock headers;
  headers["server"] = "SpdyServer 1.0";
  headers["date"] = "Mon 12 Jan 2009 12:12:12 PST";
  headers["status"] = "200";
  headers["version"] = "HTTP/1.1";
  headers["content-type"] = "text/html";
  headers["content-length"] = "12";

  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, true);
  scoped_ptr<SpdySynStreamControlFrame>
      frame1(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true,
                                    &headers));
  scoped_ptr<SpdySynStreamControlFrame>
      frame2(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true,
                                    &headers));

  // Expect the second frame to be more compact than the first.
  EXPECT_LE(frame2->length(), frame1->length());

  // Decompress the first frame
  scoped_ptr<SpdyFrame> frame3(framer.DecompressFrame(*frame1.get()));

  // Decompress the second frame
  scoped_ptr<SpdyFrame> frame4(framer.DecompressFrame(*frame2.get()));

  // Expect frames 3 & 4 to be the same.
  EXPECT_EQ(0,
      memcmp(frame3->data(), frame4->data(),
      SpdyFrame::size() + frame3->length()));
}

TEST_F(SpdyFramerTest, DecompressUncompressedFrame) {
  SpdyHeaderBlock headers;
  headers["server"] = "SpdyServer 1.0";
  headers["date"] = "Mon 12 Jan 2009 12:12:12 PST";
  headers["status"] = "200";
  headers["version"] = "HTTP/1.1";
  headers["content-type"] = "text/html";
  headers["content-length"] = "12";

  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, true);
  scoped_ptr<SpdySynStreamControlFrame>
      frame1(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, false,
                                    &headers));

  // Decompress the frame
  scoped_ptr<SpdyFrame> frame2(framer.DecompressFrame(*frame1.get()));

  EXPECT_EQ(NULL, frame2.get());
}

TEST_F(SpdyFramerTest, Basic) {
  const unsigned char input[] = {
    0x80, 0x02, 0x00, 0x01,   // SYN Stream #1
    0x00, 0x00, 0x00, 0x14,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x02, 'h', 'h',
    0x00, 0x02, 'v', 'v',

    0x80, 0x02, 0x00, 0x08,   // HEADERS on Stream #1
    0x00, 0x00, 0x00, 0x18,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x02,
    0x00, 0x02, 'h', '2',
    0x00, 0x02, 'v', '2',
    0x00, 0x02, 'h', '3',
    0x00, 0x02, 'v', '3',

    0x00, 0x00, 0x00, 0x01,   // DATA on Stream #1
    0x00, 0x00, 0x00, 0x0c,
      0xde, 0xad, 0xbe, 0xef,
      0xde, 0xad, 0xbe, 0xef,
      0xde, 0xad, 0xbe, 0xef,

    0x80, 0x02, 0x00, 0x01,   // SYN Stream #3
    0x00, 0x00, 0x00, 0x0c,
    0x00, 0x00, 0x00, 0x03,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x03,   // DATA on Stream #3
    0x00, 0x00, 0x00, 0x08,
      0xde, 0xad, 0xbe, 0xef,
      0xde, 0xad, 0xbe, 0xef,

    0x00, 0x00, 0x00, 0x01,   // DATA on Stream #1
    0x00, 0x00, 0x00, 0x04,
      0xde, 0xad, 0xbe, 0xef,

    0x80, 0x02, 0x00, 0x03,   // RST_STREAM on Stream #1
    0x00, 0x00, 0x00, 0x08,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x03,   // DATA on Stream #3
    0x00, 0x00, 0x00, 0x00,

    0x80, 0x02, 0x00, 0x03,   // RST_STREAM on Stream #3
    0x00, 0x00, 0x00, 0x08,
    0x00, 0x00, 0x00, 0x03,
    0x00, 0x00, 0x00, 0x00,
  };

  TestSpdyVisitor visitor;
  visitor.SimulateInFramer(input, sizeof(input));

  EXPECT_EQ(0, visitor.error_count_);
  EXPECT_EQ(2, visitor.syn_frame_count_);
  EXPECT_EQ(0, visitor.syn_reply_frame_count_);
  EXPECT_EQ(1, visitor.headers_frame_count_);
  EXPECT_EQ(24, visitor.data_bytes_);
  EXPECT_EQ(2, visitor.fin_frame_count_);
  EXPECT_EQ(0, visitor.fin_flag_count_);
  EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
}

// Test that the FIN flag on a data frame signifies EOF.
TEST_F(SpdyFramerTest, FinOnDataFrame) {
  const unsigned char input[] = {
    0x80, 0x02, 0x00, 0x01,   // SYN Stream #1
    0x00, 0x00, 0x00, 0x14,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x02, 'h', 'h',
    0x00, 0x02, 'v', 'v',

    0x80, 0x02, 0x00, 0x02,   // SYN REPLY Stream #1
    0x00, 0x00, 0x00, 0x10,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x02, 'a', 'a',
    0x00, 0x02, 'b', 'b',

    0x00, 0x00, 0x00, 0x01,   // DATA on Stream #1
    0x00, 0x00, 0x00, 0x0c,
      0xde, 0xad, 0xbe, 0xef,
      0xde, 0xad, 0xbe, 0xef,
      0xde, 0xad, 0xbe, 0xef,

    0x00, 0x00, 0x00, 0x01,   // DATA on Stream #1, with EOF
    0x01, 0x00, 0x00, 0x04,
      0xde, 0xad, 0xbe, 0xef,
  };

  TestSpdyVisitor visitor;
  visitor.SimulateInFramer(input, sizeof(input));

  EXPECT_EQ(0, visitor.error_count_);
  EXPECT_EQ(1, visitor.syn_frame_count_);
  EXPECT_EQ(1, visitor.syn_reply_frame_count_);
  EXPECT_EQ(0, visitor.headers_frame_count_);
  EXPECT_EQ(16, visitor.data_bytes_);
  EXPECT_EQ(0, visitor.fin_frame_count_);
  EXPECT_EQ(0, visitor.fin_flag_count_);
  EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
}

// Test that the FIN flag on a SYN reply frame signifies EOF.
TEST_F(SpdyFramerTest, FinOnSynReplyFrame) {
  const unsigned char input[] = {
    0x80, 0x02, 0x00, 0x01,   // SYN Stream #1
    0x00, 0x00, 0x00, 0x14,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x02, 'h', 'h',
    0x00, 0x02, 'v', 'v',

    0x80, 0x02, 0x00, 0x02,   // SYN REPLY Stream #1
    0x01, 0x00, 0x00, 0x10,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x01,
    0x00, 0x02, 'a', 'a',
    0x00, 0x02, 'b', 'b',
  };

  TestSpdyVisitor visitor;
  visitor.SimulateInFramer(input, sizeof(input));

  EXPECT_EQ(0, visitor.error_count_);
  EXPECT_EQ(1, visitor.syn_frame_count_);
  EXPECT_EQ(1, visitor.syn_reply_frame_count_);
  EXPECT_EQ(0, visitor.headers_frame_count_);
  EXPECT_EQ(0, visitor.data_bytes_);
  EXPECT_EQ(0, visitor.fin_frame_count_);
  EXPECT_EQ(1, visitor.fin_flag_count_);
  EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
}

// Basic compression & decompression
TEST_F(SpdyFramerTest, DataCompression) {
  SpdyFramer send_framer;
  SpdyFramer recv_framer;

  FramerSetEnableCompressionHelper(&send_framer, true);
  FramerSetEnableCompressionHelper(&recv_framer, true);

  // Mix up some SYNs and DATA frames since they use different compressors.
  const char kHeader1[] = "header1";
  const char kHeader2[] = "header2";
  const char kHeader3[] = "header3";
  const char kValue1[] = "value1";
  const char kValue2[] = "value2";
  const char kValue3[] = "value3";

  // SYN_STREAM #1
  SpdyHeaderBlock block;
  block[kHeader1] = kValue1;
  block[kHeader2] = kValue2;
  SpdyControlFlags flags(CONTROL_FLAG_NONE);
  scoped_ptr<spdy::SpdyFrame> syn_frame_1(
      send_framer.CreateSynStream(1, 0, 0, flags, true, &block));
  EXPECT_TRUE(syn_frame_1.get() != NULL);

  // DATA #1
  const char bytes[] = "this is a test test test test test!";
  scoped_ptr<SpdyFrame> data_frame_1(
      send_framer.CreateDataFrame(1, bytes, arraysize(bytes),
                                  DATA_FLAG_COMPRESSED));
  EXPECT_TRUE(data_frame_1.get() != NULL);

  // SYN_STREAM #2
  block[kHeader3] = kValue3;
  scoped_ptr<SpdyFrame> syn_frame_2(
      send_framer.CreateSynStream(3, 0, 0, flags, true, &block));
  EXPECT_TRUE(syn_frame_2.get() != NULL);

  // DATA #2
  scoped_ptr<SpdyFrame> data_frame_2(
      send_framer.CreateDataFrame(3, bytes, arraysize(bytes),
                                  DATA_FLAG_COMPRESSED));
  EXPECT_TRUE(data_frame_2.get() != NULL);

  // Now start decompressing
  scoped_ptr<SpdyFrame> decompressed;
  SpdyControlFrame* control_frame;
  SpdyDataFrame* data_frame;
  SpdyHeaderBlock decompressed_headers;

  decompressed.reset(recv_framer.DuplicateFrame(*syn_frame_1.get()));
  EXPECT_TRUE(decompressed.get() != NULL);
  EXPECT_TRUE(decompressed->is_control_frame());
  control_frame = reinterpret_cast<SpdyControlFrame*>(decompressed.get());
  EXPECT_EQ(SYN_STREAM, control_frame->type());
  EXPECT_TRUE(recv_framer.ParseHeaderBlock(
      control_frame, &decompressed_headers));
  EXPECT_EQ(2u, decompressed_headers.size());
  EXPECT_EQ(SYN_STREAM, control_frame->type());
  EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
  EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);

  decompressed.reset(recv_framer.DecompressFrame(*data_frame_1.get()));
  EXPECT_TRUE(decompressed.get() != NULL);
  EXPECT_FALSE(decompressed->is_control_frame());
  data_frame = reinterpret_cast<SpdyDataFrame*>(decompressed.get());
  EXPECT_EQ(arraysize(bytes), data_frame->length());
  EXPECT_EQ(0, memcmp(data_frame->payload(), bytes, data_frame->length()));

  decompressed.reset(recv_framer.DuplicateFrame(*syn_frame_2.get()));
  EXPECT_TRUE(decompressed.get() != NULL);
  EXPECT_TRUE(decompressed->is_control_frame());
  control_frame = reinterpret_cast<SpdyControlFrame*>(decompressed.get());
  EXPECT_EQ(control_frame->type(), SYN_STREAM);
  decompressed_headers.clear();
  EXPECT_TRUE(recv_framer.ParseHeaderBlock(
      control_frame, &decompressed_headers));
  EXPECT_EQ(3u, decompressed_headers.size());
  EXPECT_EQ(SYN_STREAM, control_frame->type());
  EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
  EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);
  EXPECT_EQ(kValue3, decompressed_headers[kHeader3]);

  decompressed.reset(recv_framer.DecompressFrame(*data_frame_2.get()));
  EXPECT_TRUE(decompressed.get() != NULL);
  EXPECT_FALSE(decompressed->is_control_frame());
  data_frame = reinterpret_cast<SpdyDataFrame*>(decompressed.get());
  EXPECT_EQ(arraysize(bytes), data_frame->length());
  EXPECT_EQ(0, memcmp(data_frame->payload(), bytes, data_frame->length()));

  // We didn't close these streams, so the compressors should be active.
  EXPECT_EQ(2, send_framer.num_stream_compressors());
  EXPECT_EQ(0, send_framer.num_stream_decompressors());
  EXPECT_EQ(0, recv_framer.num_stream_compressors());
  EXPECT_EQ(2, recv_framer.num_stream_decompressors());
}

// Verify we don't leak when we leave streams unclosed
TEST_F(SpdyFramerTest, UnclosedStreamDataCompressors) {
  SpdyFramer send_framer;

  FramerSetEnableCompressionHelper(&send_framer, false);

  const char kHeader1[] = "header1";
  const char kHeader2[] = "header2";
  const char kValue1[] = "value1";
  const char kValue2[] = "value2";

  SpdyHeaderBlock block;
  block[kHeader1] = kValue1;
  block[kHeader2] = kValue2;
  SpdyControlFlags flags(CONTROL_FLAG_NONE);
  scoped_ptr<spdy::SpdyFrame> syn_frame(
      send_framer.CreateSynStream(1, 0, 0, flags, true, &block));
  EXPECT_TRUE(syn_frame.get() != NULL);

  const char bytes[] = "this is a test test test test test!";
  scoped_ptr<SpdyFrame> send_frame(
      send_framer.CreateDataFrame(1,
                                  bytes,
                                  arraysize(bytes),
                                  DATA_FLAG_FIN));
  EXPECT_TRUE(send_frame.get() != NULL);

  // Run the inputs through the framer.
  TestSpdyVisitor visitor;
  const unsigned char* data;
  data = reinterpret_cast<const unsigned char*>(syn_frame->data());
  visitor.SimulateInFramer(data, syn_frame->length() + SpdyFrame::size());
  data = reinterpret_cast<const unsigned char*>(send_frame->data());
  visitor.SimulateInFramer(data, send_frame->length() + SpdyFrame::size());

  EXPECT_EQ(0, visitor.error_count_);
  EXPECT_EQ(1, visitor.syn_frame_count_);
  EXPECT_EQ(0, visitor.syn_reply_frame_count_);
  EXPECT_EQ(0, visitor.headers_frame_count_);
  EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_));
  EXPECT_EQ(0, visitor.fin_frame_count_);
  EXPECT_EQ(0, visitor.fin_flag_count_);
  EXPECT_EQ(1, visitor.zero_length_data_frame_count_);

  // We closed the streams, so all compressors should be down.
  EXPECT_EQ(0, visitor.framer_.num_stream_compressors());
  EXPECT_EQ(0, visitor.framer_.num_stream_decompressors());
  EXPECT_EQ(0, send_framer.num_stream_compressors());
  EXPECT_EQ(0, send_framer.num_stream_decompressors());
}

TEST_F(SpdyFramerTest, CreateDataFrame) {
  SpdyFramer framer;

  {
    const char kDescription[] = "'hello' data frame, no FIN";
    const unsigned char kFrameData[] = {
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x05,
      'h', 'e', 'l', 'l',
      'o'
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
        1, "hello", 5, DATA_FLAG_NONE));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "Data frame with negative data byte, no FIN";
    const unsigned char kFrameData[] = {
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x01,
      0xff
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
        1, "\xff", 1, DATA_FLAG_NONE));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "'hello' data frame, with FIN";
    const unsigned char kFrameData[] = {
      0x00, 0x00, 0x00, 0x01,
      0x01, 0x00, 0x00, 0x05,
      'h', 'e', 'l', 'l',
      'o'
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
        1, "hello", 5, DATA_FLAG_FIN));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "Empty data frame";
    const unsigned char kFrameData[] = {
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
        1, "", 0, DATA_FLAG_NONE));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "Data frame with max stream ID";
    const unsigned char kFrameData[] = {
      0x7f, 0xff, 0xff, 0xff,
      0x01, 0x00, 0x00, 0x05,
      'h', 'e', 'l', 'l',
      'o'
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
        0x7fffffff, "hello", 5, DATA_FLAG_FIN));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateSynStreamUncompressed) {
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, false);

  {
    const char kDescription[] = "SYN_STREAM frame, lowest pri, no FIN";

    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "bar";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x20,
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00,
      0xC0, 0x00, 0x00, 0x02,
      0x00, 0x03, 'b',  'a',
      'r',  0x00, 0x03, 'f',
      'o',  'o',  0x00, 0x03,
      'f',  'o',  'o',  0x00,
      0x03, 'b',  'a',  'r'
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSynStream(
        1, 0, SPDY_PRIORITY_LOWEST, CONTROL_FLAG_NONE,
        false, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] =
        "SYN_STREAM frame with a 0-length header name, highest pri, FIN, "
        "max stream ID";

    SpdyHeaderBlock headers;
    headers[""] = "foo";
    headers["foo"] = "bar";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x01,
      0x01, 0x00, 0x00, 0x1D,
      0x7f, 0xff, 0xff, 0xff,
      0x7f, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x03,
      'f',  'o',  'o',  0x00,
      0x03, 'f',  'o',  'o',
      0x00, 0x03, 'b',  'a',
      'r'
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSynStream(
        0x7fffffff, 0x7fffffff, SPDY_PRIORITY_HIGHEST, CONTROL_FLAG_FIN,
        false, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] =
        "SYN_STREAM frame with a 0-length header val, highest pri, FIN, "
        "max stream ID";

    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x01,
      0x01, 0x00, 0x00, 0x1D,
      0x7f, 0xff, 0xff, 0xff,
      0x7f, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x02,
      0x00, 0x03, 'b',  'a',
      'r',  0x00, 0x03, 'f',
      'o',  'o',  0x00, 0x03,
      'f',  'o',  'o',  0x00,
      0x00
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSynStream(
        0x7fffffff, 0x7fffffff, SPDY_PRIORITY_HIGHEST, CONTROL_FLAG_FIN,
        false, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateSynStreamCompressed) {
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, true);

  {
    const char kDescription[] =
        "SYN_STREAM frame, lowest pri, no FIN";

    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "bar";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x25,
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00,
      0xC0, 0x00, 0x38, 0xea,
      0xdf, 0xa2, 0x51, 0xb2,
      0x62, 0x60, 0x62, 0x60,
      0x4e, 0x4a, 0x2c, 0x62,
      0x60, 0x4e, 0xcb, 0xcf,
      0x87, 0x12, 0x40, 0x2e,
      0x00, 0x00, 0x00, 0xff,
      0xff
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSynStream(
        1, 0, SPDY_PRIORITY_LOWEST, CONTROL_FLAG_NONE,
        true, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) {
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, false);

  {
    const char kDescription[] = "SYN_REPLY frame, no FIN";

    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "bar";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x1C,
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x02,
      0x00, 0x03, 'b',  'a',
      'r',  0x00, 0x03, 'f',
      'o',  'o',  0x00, 0x03,
      'f',  'o',  'o',  0x00,
      0x03, 'b',  'a',  'r'
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
        1, CONTROL_FLAG_NONE, false, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] =
        "SYN_REPLY frame with a 0-length header name, FIN, max stream ID";

    SpdyHeaderBlock headers;
    headers[""] = "foo";
    headers["foo"] = "bar";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x02,
      0x01, 0x00, 0x00, 0x19,
      0x7f, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x03,
      'f',  'o',  'o',  0x00,
      0x03, 'f',  'o',  'o',
      0x00, 0x03, 'b',  'a',
      'r'
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
        0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] =
        "SYN_REPLY frame with a 0-length header val, FIN, max stream ID";

    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x02,
      0x01, 0x00, 0x00, 0x19,
      0x7f, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x02,
      0x00, 0x03, 'b',  'a',
      'r',  0x00, 0x03, 'f',
      'o',  'o',  0x00, 0x03,
      'f',  'o',  'o',  0x00,
      0x00
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
        0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateSynReplyCompressed) {
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, true);

  {
    const char kDescription[] = "SYN_REPLY frame, no FIN";

    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "bar";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x21,
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x38, 0xea,
      0xdf, 0xa2, 0x51, 0xb2,
      0x62, 0x60, 0x62, 0x60,
      0x4e, 0x4a, 0x2c, 0x62,
      0x60, 0x4e, 0xcb, 0xcf,
      0x87, 0x12, 0x40, 0x2e,
      0x00, 0x00, 0x00, 0xff,
      0xff
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
        1, CONTROL_FLAG_NONE, true, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateRstStream) {
  SpdyFramer framer;

  {
    const char kDescription[] = "RST_STREAM frame";
    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x03,
      0x00, 0x00, 0x00, 0x08,
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x01,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(1, PROTOCOL_ERROR));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "RST_STREAM frame with max stream ID";
    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x03,
      0x00, 0x00, 0x00, 0x08,
      0x7f, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x01,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF,
                                                       PROTOCOL_ERROR));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "RST_STREAM frame with max status code";
    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x03,
      0x00, 0x00, 0x00, 0x08,
      0x7f, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x06,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF,
                                                       INTERNAL_ERROR));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateSettings) {
  SpdyFramer framer;

  {
    const char kDescription[] = "Basic SETTINGS frame";

    SpdySettings settings;
    settings.push_back(SpdySetting(0x00000000, 0x00000000));
    settings.push_back(SpdySetting(0xffffffff, 0x00000001));
    settings.push_back(SpdySetting(0xff000001, 0x00000002));

    // Duplicates allowed
    settings.push_back(SpdySetting(0x01000002, 0x00000003));
    settings.push_back(SpdySetting(0x01000002, 0x00000003));

    settings.push_back(SpdySetting(0x01000003, 0x000000ff));
    settings.push_back(SpdySetting(0x01000004, 0xff000001));
    settings.push_back(SpdySetting(0x01000004, 0xffffffff));

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x04,
      0x00, 0x00, 0x00, 0x44,
      0x00, 0x00, 0x00, 0x08,
      0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00,
      0xff, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x01,
      0xff, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x02,
      0x01, 0x00, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x03,
      0x01, 0x00, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x03,
      0x01, 0x00, 0x00, 0x03,
      0x00, 0x00, 0x00, 0xff,
      0x01, 0x00, 0x00, 0x04,
      0xff, 0x00, 0x00, 0x01,
      0x01, 0x00, 0x00, 0x04,
      0xff, 0xff, 0xff, 0xff,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "Empty SETTINGS frame";

    SpdySettings settings;

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x04,
      0x00, 0x00, 0x00, 0x04,
      0x00, 0x00, 0x00, 0x00,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateNopFrame) {
  SpdyFramer framer;

  {
    const char kDescription[] = "NOOP frame";
    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x05,
      0x00, 0x00, 0x00, 0x00,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateNopFrame());
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateGoAway) {
  SpdyFramer framer;

  {
    const char kDescription[] = "GOAWAY frame";
    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x07,
      0x00, 0x00, 0x00, 0x04,
      0x00, 0x00, 0x00, 0x00,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "GOAWAY frame with max stream ID";
    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x07,
      0x00, 0x00, 0x00, 0x04,
      0x7f, 0xff, 0xff, 0xff,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0x7FFFFFFF));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateHeadersUncompressed) {
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, false);

  {
    const char kDescription[] = "HEADERS frame, no FIN";

    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "bar";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x08,
      0x00, 0x00, 0x00, 0x1C,
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x02,
      0x00, 0x03, 'b',  'a',
      'r',  0x00, 0x03, 'f',
      'o',  'o',  0x00, 0x03,
      'f',  'o',  'o',  0x00,
      0x03, 'b',  'a',  'r'
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
        1, CONTROL_FLAG_NONE, false, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] =
        "HEADERS frame with a 0-length header name, FIN, max stream ID";

    SpdyHeaderBlock headers;
    headers[""] = "foo";
    headers["foo"] = "bar";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x08,
      0x01, 0x00, 0x00, 0x19,
      0x7f, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x03,
      'f',  'o',  'o',  0x00,
      0x03, 'f',  'o',  'o',
      0x00, 0x03, 'b',  'a',
      'r'
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
        0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] =
        "HEADERS frame with a 0-length header val, FIN, max stream ID";

    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x08,
      0x01, 0x00, 0x00, 0x19,
      0x7f, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x02,
      0x00, 0x03, 'b',  'a',
      'r',  0x00, 0x03, 'f',
      'o',  'o',  0x00, 0x03,
      'f',  'o',  'o',  0x00,
      0x00
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
        0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateHeadersCompressed) {
  SpdyFramer framer;
  FramerSetEnableCompressionHelper(&framer, true);

  {
    const char kDescription[] = "HEADERS frame, no FIN";

    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "bar";

    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x08,
      0x00, 0x00, 0x00, 0x21,
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x38, 0xea,
      0xdf, 0xa2, 0x51, 0xb2,
      0x62, 0x60, 0x62, 0x60,
      0x4e, 0x4a, 0x2c, 0x62,
      0x60, 0x4e, 0xcb, 0xcf,
      0x87, 0x12, 0x40, 0x2e,
      0x00, 0x00, 0x00, 0xff,
      0xff
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
        1, CONTROL_FLAG_NONE, true, &headers));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

TEST_F(SpdyFramerTest, CreateWindowUpdate) {
  SpdyFramer framer;

  {
    const char kDescription[] = "WINDOW_UPDATE frame";
    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x09,
      0x00, 0x00, 0x00, 0x08,
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x01,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 1));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "WINDOW_UPDATE frame with max stream ID";
    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x09,
      0x00, 0x00, 0x00, 0x08,
      0x7f, 0xff, 0xff, 0xff,
      0x00, 0x00, 0x00, 0x01,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(0x7FFFFFFF, 1));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }

  {
    const char kDescription[] = "WINDOW_UPDATE frame with max window delta";
    const unsigned char kFrameData[] = {
      0x80, 0x02, 0x00, 0x09,
      0x00, 0x00, 0x00, 0x08,
      0x00, 0x00, 0x00, 0x01,
      0x7f, 0xff, 0xff, 0xff,
    };
    scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x7FFFFFFF));
    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
  }
}

// This test case reproduces conditions that caused ExpandControlFrameBuffer to
// fail to expand the buffer control frame buffer when it should have, allowing
// the framer to overrun the buffer, and smash other heap contents. This test
// relies on the debug version of the heap manager, which checks for buffer
// overrun errors during delete processing. Regression test for b/2974814.
TEST_F(SpdyFramerTest, ExpandBuffer_HeapSmash) {
  // Sweep through the area of problematic values, to make sure we always cover
  // the danger zone, even if it moves around at bit due to SPDY changes.
  for (uint16 val2_len = SpdyFramer::kControlFrameBufferInitialSize - 50;
       val2_len < SpdyFramer::kControlFrameBufferInitialSize;
       val2_len++) {
    std::string val2 = std::string(val2_len, 'a');
    SpdyHeaderBlock headers;
    headers["bar"] = "foo";
    headers["foo"] = "baz";
    headers["grue"] = val2.c_str();
    SpdyFramer framer;
    scoped_ptr<SpdySynStreamControlFrame> template_frame(
        framer.CreateSynStream(1,                      // stream_id
                               0,                      // associated_stream_id
                               1,                      // priority
                               CONTROL_FLAG_NONE,
                               false,                  // compress
                               &headers));
    EXPECT_TRUE(template_frame.get() != NULL);
    TestSpdyVisitor visitor;
    visitor.SimulateInFramer(
        reinterpret_cast<unsigned char*>(template_frame.get()->data()),
         template_frame.get()->length() + SpdyControlFrame::size());
    EXPECT_EQ(1, visitor.syn_frame_count_);
  }
}

std::string RandomString(int length) {
  std::string rv;
  for (int index = 0; index < length; index++)
    rv += static_cast<char>('a' + (rand() % 26));
  return rv;
}

// Stress that we can handle a really large header block compression and
// decompression.
TEST_F(SpdyFramerTest, HugeHeaderBlock) {
  // Loop targetting various sizes which will potentially jam up the
  // frame compressor/decompressor.
  SpdyFramer compress_framer;
  SpdyFramer decompress_framer;
  for (size_t target_size = 1024;
       target_size < SpdyFramer::kControlFrameBufferInitialSize;
       target_size += 1024) {
    SpdyHeaderBlock headers;
    for (size_t index = 0; index < target_size; ++index) {
      std::string name = RandomString(4);
      std::string value = RandomString(8);
      headers[name] = value;
    }

    // Encode the header block into a SynStream frame.
    scoped_ptr<SpdySynStreamControlFrame> frame(
        compress_framer.CreateSynStream(1,
                                        0,
                                        1,
                                        CONTROL_FLAG_NONE,
                                        true,
                                        &headers));
    // The point of this test is to exercise the limits.  So, it is ok if the
    // frame was too large to encode, or if the decompress fails.  We just want
    // to make sure we don't crash.
    if (frame.get() != NULL) {
      // Now that same header block should decompress just fine.
      SpdyHeaderBlock new_headers;
      decompress_framer.ParseHeaderBlock(frame.get(), &new_headers);
    }
  }
}

}  // namespace