You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ji...@apache.org on 2014/04/28 12:55:22 UTC

svn commit: r1590597 [8/14] - in /httpd/httpd/trunk/modules/spdy: ./ apache/ apache/filters/ apache/testing/ common/ common/testing/ support/ support/base/ support/base/metrics/ support/build/ support/install/ support/install/common/ support/install/de...

Added: httpd/httpd/trunk/modules/spdy/common/spdy_stream_test.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/spdy_stream_test.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/spdy_stream_test.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/spdy_stream_test.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,596 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_stream.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/shared_flow_control_window.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/testing/async_task_runner.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using mod_spdy::testing::IsDataFrame;
+using mod_spdy::testing::IsRstStream;
+using mod_spdy::testing::IsWindowUpdate;
+
+namespace {
+
+const net::SpdyStreamId kStreamId = 1;
+const net::SpdyStreamId kAssocStreamId = 0;
+const int32 kInitServerPushDepth = 0;
+const net::SpdyPriority kPriority = 2;
+
+class MockSpdyServerPushInterface : public mod_spdy::SpdyServerPushInterface {
+ public:
+    MOCK_METHOD4(StartServerPush,
+                 mod_spdy::SpdyServerPushInterface::PushStatus(
+                     net::SpdyStreamId associated_stream_id,
+                     int32 server_push_depth,
+                     net::SpdyPriority priority,
+                     const net::SpdyNameValueBlock& request_headers));
+};
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// data frame with the given payload and FLAG_FIN setting.
+void ExpectDataFrame(mod_spdy::SpdyFramePriorityQueue* output_queue,
+                     base::StringPiece data, bool flag_fin) {
+  net::SpdyFrameIR* raw_frame;
+  ASSERT_TRUE(output_queue->BlockingPop(
+      base::TimeDelta::FromMilliseconds(100), &raw_frame));
+  scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+  EXPECT_THAT(*frame, IsDataFrame(kStreamId, flag_fin, data));
+}
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// RST_STREAM frame with the given status code.
+void ExpectRstStream(mod_spdy::SpdyFramePriorityQueue* output_queue,
+                     net::SpdyRstStreamStatus status) {
+  net::SpdyFrameIR* raw_frame;
+  ASSERT_TRUE(output_queue->BlockingPop(
+      base::TimeDelta::FromMilliseconds(100), &raw_frame));
+  scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+  EXPECT_THAT(*frame, IsRstStream(kStreamId, status));
+}
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// WINDOW_UPDATE frame with the given delta.
+void ExpectWindowUpdate(mod_spdy::SpdyFramePriorityQueue* output_queue,
+                        uint32 delta) {
+  net::SpdyFrameIR* raw_frame;
+  ASSERT_TRUE(output_queue->BlockingPop(
+      base::TimeDelta::FromMilliseconds(100), &raw_frame));
+  scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+  EXPECT_THAT(*frame, IsWindowUpdate(kStreamId, delta));
+}
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// WINDOW_UPDATE frame, for stream zero, with the given delta.
+void ExpectSessionWindowUpdate(mod_spdy::SpdyFramePriorityQueue* output_queue,
+                               uint32 delta) {
+  net::SpdyFrameIR* raw_frame;
+  ASSERT_TRUE(output_queue->BlockingPop(
+      base::TimeDelta::FromMilliseconds(100), &raw_frame));
+  scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+  EXPECT_THAT(*frame, IsWindowUpdate(0, delta));
+}
+
+// When run, a SendDataTask sends the given data to the given stream.
+class SendDataTask : public mod_spdy::testing::AsyncTaskRunner::Task {
+ public:
+  SendDataTask(mod_spdy::SpdyStream* stream, base::StringPiece data,
+               bool flag_fin)
+      : stream_(stream), data_(data), flag_fin_(flag_fin) {}
+  virtual void Run() {
+    stream_->SendOutputDataFrame(data_, flag_fin_);
+  }
+ private:
+  mod_spdy::SpdyStream* const stream_;
+  const base::StringPiece data_;
+  const bool flag_fin_;
+  DISALLOW_COPY_AND_ASSIGN(SendDataTask);
+};
+
+// Test that the flow control features are disabled for SPDY v2.
+TEST(SpdyStreamTest, NoFlowControlInSpdy2) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  const int32 initial_window_size = 10;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_2, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+      NULL, &pusher);
+
+  // Send more data than can fit in the initial window size.
+  const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+  stream.SendOutputDataFrame(data, true);
+
+  // We should get all the data out in one frame anyway, because we're using
+  // SPDY v2 and the stream shouldn't be using flow control.
+  ExpectDataFrame(&output_queue, data, true);
+  EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that flow control works correctly for SPDY/3.
+TEST(SpdyStreamTest, HasFlowControlInSpdy3) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  mod_spdy::SharedFlowControlWindow shared_window(1000, 7);
+  MockSpdyServerPushInterface pusher;
+  const int32 initial_window_size = 10;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+      &shared_window, &pusher);
+
+  // Send more data than can fit in the initial window size.
+  const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+  mod_spdy::testing::AsyncTaskRunner runner(
+      new SendDataTask(&stream, data, true));
+  ASSERT_TRUE(runner.Start());
+
+  // We should get a single frame out with the first initial_window_size=10
+  // bytes (and no FLAG_FIN yet), and then the task should be blocked for now.
+  ExpectDataFrame(&output_queue, "abcdefghij", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+
+  // After increasing the window size by eight, we should get eight more bytes,
+  // and then we should still be blocked.
+  stream.AdjustOutputWindowSize(8);
+  ExpectDataFrame(&output_queue, "klmnopqr", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+
+  // Finally, we increase the window size by fifteen.  We should get the last
+  // eight bytes of data out (with FLAG_FIN now set), the task should be
+  // completed, and the remaining window size should be seven.
+  stream.AdjustOutputWindowSize(15);
+  ExpectDataFrame(&output_queue, "stuvwxyz", true);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectSetWithinMillis(100);
+  EXPECT_EQ(7, stream.current_output_window_size());
+}
+
+// Test that the session flow control window works correctly for SPDY/3.1.
+TEST(SpdyStreamTest, SessionWindowInSpdy31) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  mod_spdy::SharedFlowControlWindow shared_window(1000, 7);
+  MockSpdyServerPushInterface pusher;
+  const int32 initial_window_size = 10;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3_1, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, initial_window_size,
+      &output_queue, &shared_window, &pusher);
+
+  // Send more data than can fit in the initial window size.
+  const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+  mod_spdy::testing::AsyncTaskRunner runner(
+      new SendDataTask(&stream, data, true));
+  ASSERT_TRUE(runner.Start());
+
+  // The stream window size is 10, but the session window size is only 7.  So
+  // we should only get 7 bytes at first.
+  ExpectDataFrame(&output_queue, "abcdefg", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+  EXPECT_EQ(0, shared_window.current_output_window_size());
+
+  // Now we increase the shared window size to 8.  The stream window size is
+  // only 3, so we should get just 3 more bytes.
+  EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(8));
+  ExpectDataFrame(&output_queue, "hij", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+  EXPECT_EQ(5, shared_window.current_output_window_size());
+  EXPECT_EQ(0, stream.current_output_window_size());
+
+  // Next, increase the stream window by 20 bytes.  The shared window is only
+  // 5, so we get 5 bytes.
+  stream.AdjustOutputWindowSize(20);
+  ExpectDataFrame(&output_queue, "klmno", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+  EXPECT_EQ(0, shared_window.current_output_window_size());
+
+  // Finally, we increase the shared window size by 20.  We should get the last
+  // 11 bytes of data out (with FLAG_FIN now set), and the task should be
+  // completed.
+  EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(20));
+  ExpectDataFrame(&output_queue, "pqrstuvwxyz", true);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectSetWithinMillis(100);
+  EXPECT_EQ(9, shared_window.current_output_window_size());
+  EXPECT_EQ(4, stream.current_output_window_size());
+}
+
+// Test that flow control is well-behaved when the stream is aborted.
+TEST(SpdyStreamTest, FlowControlAbort) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  const int32 initial_window_size = 7;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+      NULL, &pusher);
+
+  // Send more data than can fit in the initial window size.
+  const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+  mod_spdy::testing::AsyncTaskRunner runner(
+      new SendDataTask(&stream, data, true));
+  ASSERT_TRUE(runner.Start());
+
+  // We should get a single frame out with the first initial_window_size=7
+  // bytes (and no FLAG_FIN yet), and then the task should be blocked for now.
+  ExpectDataFrame(&output_queue, "abcdefg", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+  EXPECT_FALSE(stream.is_aborted());
+
+  // We now abort with a RST_STREAM frame.  We should get the RST_STREAM frame
+  // out, but no more data, and the call to SendOutputDataFrame should return
+  // even though the rest of the data was never sent.
+  stream.AbortWithRstStream(net::RST_STREAM_PROTOCOL_ERROR);
+  EXPECT_TRUE(stream.is_aborted());
+  ExpectRstStream(&output_queue, net::RST_STREAM_PROTOCOL_ERROR);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectSetWithinMillis(100);
+
+  // Now that we're aborted, any attempt to send more frames should be ignored.
+  stream.SendOutputDataFrame("foobar", false);
+  net::SpdyNameValueBlock headers;
+  headers["x-foo"] = "bar";
+  stream.SendOutputHeaders(headers, true);
+  EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that we abort the stream with FLOW_CONTROL_ERROR if the client
+// incorrectly overflows the 31-bit window size value.
+TEST(SpdyStreamTest, FlowControlOverflow) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, 0x60000000, &output_queue, NULL,
+      &pusher);
+
+  // Increase the window size so large that it overflows.  We should get a
+  // RST_STREAM frame and the stream should be aborted.
+  EXPECT_FALSE(stream.is_aborted());
+  stream.AdjustOutputWindowSize(0x20000000);
+  EXPECT_TRUE(stream.is_aborted());
+  ExpectRstStream(&output_queue, net::RST_STREAM_FLOW_CONTROL_ERROR);
+  EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that flow control works correctly even if the window size is
+// temporarily negative.
+TEST(SpdyStreamTest, NegativeWindowSize) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  const int32 initial_window_size = 10;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+      NULL, &pusher);
+
+  // Send more data than can fit in the initial window size.
+  const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+  mod_spdy::testing::AsyncTaskRunner runner(
+      new SendDataTask(&stream, data, true));
+  ASSERT_TRUE(runner.Start());
+
+  // We should get a single frame out with the first initial_window_size=10
+  // bytes (and no FLAG_FIN yet), and then the task should be blocked for now.
+  ExpectDataFrame(&output_queue, "abcdefghij", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+  EXPECT_EQ(0, stream.current_output_window_size());
+
+  // Adjust the window size down (as if due to a SETTINGS frame reducing the
+  // initial window size).  Our current window size should now be negative, and
+  // we should still be blocked.
+  stream.AdjustOutputWindowSize(-5);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+  EXPECT_EQ(-5, stream.current_output_window_size());
+
+  // Adjust the initial window size up, but not enough to be positive.  We
+  // should still be blocked.
+  stream.AdjustOutputWindowSize(4);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+  EXPECT_EQ(-1, stream.current_output_window_size());
+
+  // Adjust the initial window size up again.  Now we should get a few more
+  // bytes out.
+  stream.AdjustOutputWindowSize(4);
+  ExpectDataFrame(&output_queue, "klm", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectNotSet();
+  EXPECT_EQ(0, stream.current_output_window_size());
+
+  // Finally, open the floodgates; we should get the rest of the data.
+  stream.AdjustOutputWindowSize(800);
+  ExpectDataFrame(&output_queue, "nopqrstuvwxyz", true);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  runner.notification()->ExpectSetWithinMillis(100);
+  EXPECT_EQ(787, stream.current_output_window_size());
+}
+
+// Test that we handle sending empty DATA frames correctly in SPDY v2.
+TEST(SpdyStreamTest, SendEmptyDataFrameInSpdy2) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_2, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+      &output_queue, NULL, &pusher);
+
+  // Try to send an empty data frame without FLAG_FIN.  It should be
+  // suppressed.
+  stream.SendOutputDataFrame("", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+
+  // Now send an empty data frame _with_ FLAG_FIN.  It should _not_ be
+  // suppressed.
+  stream.SendOutputDataFrame("", true);
+  ExpectDataFrame(&output_queue, "", true);
+  EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that we handle sending empty DATA frames correctly in SPDY v3.
+TEST(SpdyStreamTest, SendEmptyDataFrameInSpdy3) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  const int32 initial_window_size = 10;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+      NULL, &pusher);
+
+  // Try to send an empty data frame without FLAG_FIN.  It should be
+  // suppressed.
+  stream.SendOutputDataFrame("", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(initial_window_size, stream.current_output_window_size());
+
+  // Send one window's worth of data.  It should get sent successfully.
+  const std::string data(initial_window_size, 'x');
+  stream.SendOutputDataFrame(data, false);
+  ExpectDataFrame(&output_queue, data, false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(0, stream.current_output_window_size());
+
+  // Try to send another empty data frame without FLAG_FIN.  It should be
+  // suppressed, and we shouldn't block, even though the window size is zero.
+  stream.SendOutputDataFrame("", false);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(0, stream.current_output_window_size());
+
+  // Now send an empty data frame _with_ FLAG_FIN.  It should _not_ be
+  // suppressed, and we still shouldn't block.
+  stream.SendOutputDataFrame("", true);
+  ExpectDataFrame(&output_queue, "", true);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(0, stream.current_output_window_size());
+}
+
+TEST(SpdyStreamTest, InputFlowControlInSpdy3) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+      &output_queue, NULL, &pusher);
+
+  // The initial window size is 64K.
+  EXPECT_EQ(65536, stream.current_input_window_size());
+
+  // Post a SYN_STREAM frame to the input.  This should not affect the input
+  // window size.
+  net::SpdyNameValueBlock request_headers;
+  request_headers[mod_spdy::http::kContentLength] = "4000";
+  request_headers[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+  request_headers[mod_spdy::spdy::kSpdy3Method] = "GET";
+  request_headers[mod_spdy::spdy::kSpdy3Path] = "/index.html";
+  request_headers[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+  scoped_ptr<net::SpdySynStreamIR> syn_stream(
+      new net::SpdySynStreamIR(kStreamId));
+  syn_stream->set_associated_to_stream_id(kAssocStreamId);
+  syn_stream->set_priority(kPriority);
+  syn_stream->GetMutableNameValueBlock()->insert(
+      request_headers.begin(), request_headers.end());
+  stream.PostInputFrame(syn_stream.release());
+  EXPECT_EQ(65536, stream.current_input_window_size());
+
+  // Send a little bit of data.  This should reduce the input window size.
+  const std::string data1("abcdefghij");
+  stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(65526, stream.current_input_window_size());
+
+  // Inform the stream that we have consumed this data.  However, we shouldn't
+  // yet send a WINDOW_UPDATE frame for so small an amount, so the window size
+  // should stay the same.
+  stream.OnInputDataConsumed(10);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(65526, stream.current_input_window_size());
+
+  // Send the rest of the data.  This should further reduce the input window
+  // size.
+  const std::string data2(9000, 'x');
+  scoped_ptr<net::SpdyDataIR> data_frame(
+      new net::SpdyDataIR(kStreamId, data2));
+  data_frame->set_fin(true);
+  stream.PostInputFrame(data_frame.release());
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(56526, stream.current_input_window_size());
+
+  // Inform the stream that we have consumed a bit more of the data.  However,
+  // we still shouldn't yet send a WINDOW_UPDATE frame, and the window size
+  // should still stay the same.
+  stream.OnInputDataConsumed(10);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(56526, stream.current_input_window_size());
+
+  // Now say that we've consumed a whole bunch of data.  At this point, we
+  // should get a WINDOW_UPDATE frame for everything consumed so far, and the
+  // window size should increase accordingly.
+  stream.OnInputDataConsumed(8900);
+  ExpectWindowUpdate(&output_queue, 8920);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(65446, stream.current_input_window_size());
+
+  // Consume the last of the data.  This is now just a little bit, so no need
+  // for a WINDOW_UPDATE here.
+  stream.OnInputDataConsumed(90);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(65446, stream.current_input_window_size());
+}
+
+TEST(SpdyStreamTest, InputFlowControlInSpdy31) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  mod_spdy::SharedFlowControlWindow shared_window(
+      net::kSpdyStreamInitialWindowSize,
+      net::kSpdyStreamInitialWindowSize);
+  MockSpdyServerPushInterface pusher;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3_1, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+      &output_queue, &shared_window, &pusher);
+
+  // The initial window size is 64K.
+  EXPECT_EQ(65536, stream.current_input_window_size());
+
+  // Post a SYN_STREAM frame to the input.  This should not affect the input
+  // window size.
+  net::SpdyHeaderBlock request_headers;
+  request_headers[mod_spdy::http::kContentLength] = "4000";
+  request_headers[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+  request_headers[mod_spdy::spdy::kSpdy3Method] = "GET";
+  request_headers[mod_spdy::spdy::kSpdy3Path] = "/index.html";
+  request_headers[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+
+  scoped_ptr<net::SpdySynStreamIR> syn_stream(
+      new net::SpdySynStreamIR(kStreamId));
+  syn_stream->set_associated_to_stream_id(kAssocStreamId);
+  syn_stream->set_priority(kPriority);
+  syn_stream->GetMutableNameValueBlock()->insert(
+      request_headers.begin(), request_headers.end());
+  stream.PostInputFrame(syn_stream.release());
+  EXPECT_EQ(65536, stream.current_input_window_size());
+
+  // Send a little bit of data.  This should reduce the input window size.
+  const std::string data1("abcdefghij");
+  EXPECT_TRUE(shared_window.OnReceiveInputData(data1.size()));
+  stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(65526, stream.current_input_window_size());
+
+  // Inform the stream that we have consumed this data.  However, we shouldn't
+  // yet send a WINDOW_UPDATE frame for so small an amount, so the window size
+  // should stay the same.
+  stream.OnInputDataConsumed(10);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(65526, stream.current_input_window_size());
+
+  // Send the rest of the data.  This should further reduce the input window
+  // size.
+  const std::string data2(9000, 'x');
+  scoped_ptr<net::SpdyDataIR> data_frame(
+      new net::SpdyDataIR(kStreamId, data2));
+  data_frame->set_fin(true);
+  EXPECT_TRUE(shared_window.OnReceiveInputData(data2.size()));
+  stream.PostInputFrame(data_frame.release());
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(56526, stream.current_input_window_size());
+
+  // Inform the stream that we have consumed a bit more of the data.  However,
+  // we still shouldn't yet send a WINDOW_UPDATE frame, and the window size
+  // should still stay the same.
+  stream.OnInputDataConsumed(10);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(56526, stream.current_input_window_size());
+
+  // Now say that we've consumed a whole bunch of data.  At this point, we
+  // should get a WINDOW_UPDATE frame for everything consumed so far, and the
+  // window size should increase accordingly.
+  stream.OnInputDataConsumed(8900);
+  ExpectSessionWindowUpdate(&output_queue, 8920);
+  ExpectWindowUpdate(&output_queue, 8920);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(65446, stream.current_input_window_size());
+
+  // Consume the last of the data.  This is now just a little bit, so no need
+  // for a WINDOW_UPDATE here.
+  stream.OnInputDataConsumed(90);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_EQ(65446, stream.current_input_window_size());
+}
+
+TEST(SpdyStreamTest, InputFlowControlError) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+      &output_queue, NULL, &pusher);
+
+  // Send a bunch of data.  This should reduce the input window size.
+  const std::string data1(1000, 'x');
+  for (int i = 0; i < 65; ++i) {
+    EXPECT_EQ(65536 - i * 1000, stream.current_input_window_size());
+    stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+    EXPECT_TRUE(output_queue.IsEmpty());
+  }
+  EXPECT_EQ(536, stream.current_input_window_size());
+  EXPECT_FALSE(stream.is_aborted());
+
+  // Send a bit more data than there is room in the window size.  This should
+  // trigger a RST_STREAM.
+  const std::string data2(537, 'y');
+  stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data2));
+  ExpectRstStream(&output_queue, net::RST_STREAM_FLOW_CONTROL_ERROR);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_TRUE(stream.is_aborted());
+}
+
+TEST(SpdyStreamTest, NoInputFlowControlInSpdy2) {
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_2, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+      &output_queue, NULL, &pusher);
+
+  // Send more data than will fit in the window size.  However, we shouldn't
+  // get an error, because this is SPDY/2 and there is no flow control.
+  const std::string data1(1000, 'x');
+  for (int i = 0; i < 70; ++i) {
+    stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+    EXPECT_TRUE(output_queue.IsEmpty());
+    EXPECT_FALSE(stream.is_aborted());
+  }
+}
+
+}  // namespace

Added: httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,337 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_to_http_converter.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"  // for Int64ToString
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/http_request_visitor_interface.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+namespace {
+
+// Generate an HTTP request line from the given SPDY header block by calling
+// the OnStatusLine() method of the given visitor, and return true.  If there's
+// an error, this will return false without calling any methods on the visitor.
+bool GenerateRequestLine(spdy::SpdyVersion spdy_version,
+                         const net::SpdyHeaderBlock& block,
+                         HttpRequestVisitorInterface* visitor) {
+  const bool spdy2 = spdy_version < spdy::SPDY_VERSION_3;
+  net::SpdyHeaderBlock::const_iterator method = block.find(
+      spdy2 ? spdy::kSpdy2Method : spdy::kSpdy3Method);
+  net::SpdyHeaderBlock::const_iterator scheme = block.find(
+      spdy2 ? spdy::kSpdy2Scheme : spdy::kSpdy3Scheme);
+  net::SpdyHeaderBlock::const_iterator host = block.find(
+      spdy2 ? http::kHost : spdy::kSpdy3Host);
+  net::SpdyHeaderBlock::const_iterator path = block.find(
+      spdy2 ? spdy::kSpdy2Url : spdy::kSpdy3Path);
+  net::SpdyHeaderBlock::const_iterator version = block.find(
+      spdy2 ? spdy::kSpdy2Version : spdy::kSpdy3Version);
+
+  if (method == block.end() ||
+      scheme == block.end() ||
+      host == block.end() ||
+      path == block.end() ||
+      version == block.end()) {
+    return false;
+  }
+
+  visitor->OnRequestLine(method->second, path->second, version->second);
+  return true;
+}
+
+// Convert the given SPDY header into HTTP header(s) by splitting on NUL bytes
+// calling the specified method (either OnLeadingHeader or OnTrailingHeader) of
+// the given visitor.
+template <void(HttpRequestVisitorInterface::*OnHeader)(
+    const base::StringPiece& key, const base::StringPiece& value)>
+void InsertHeader(const base::StringPiece key,
+                  const base::StringPiece value,
+                  HttpRequestVisitorInterface* visitor) {
+  // Split header values on null characters, emitting a separate
+  // header key-value pair for each substring. Logic from
+  // net/spdy/spdy_session.cc
+  for (size_t start = 0, end = 0; end != value.npos; start = end) {
+    start = value.find_first_not_of('\0', start);
+    if (start == value.npos) {
+      break;
+    }
+    end = value.find('\0', start);
+    (visitor->*OnHeader)(key, (end != value.npos ?
+                               value.substr(start, (end - start)) :
+                               value.substr(start)));
+  }
+}
+
+}  // namespace
+
+SpdyToHttpConverter::SpdyToHttpConverter(spdy::SpdyVersion spdy_version,
+                                         HttpRequestVisitorInterface* visitor)
+    : spdy_version_(spdy_version),
+      visitor_(visitor),
+      state_(NO_FRAMES_YET),
+      use_chunking_(true),
+      seen_accept_encoding_(false) {
+  DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+  CHECK(visitor);
+}
+
+SpdyToHttpConverter::~SpdyToHttpConverter() {}
+
+// static
+const char* SpdyToHttpConverter::StatusString(Status status) {
+  switch (status) {
+    case SPDY_CONVERTER_SUCCESS:  return "SPDY_CONVERTER_SUCCESS";
+    case FRAME_BEFORE_SYN_STREAM: return "FRAME_BEFORE_SYN_STREAM";
+    case FRAME_AFTER_FIN:         return "FRAME_AFTER_FIN";
+    case EXTRA_SYN_STREAM:        return "EXTRA_SYN_STREAM";
+    case INVALID_HEADER_BLOCK:    return "INVALID_HEADER_BLOCK";
+    case BAD_REQUEST:             return "BAD_REQUEST";
+    default:
+      LOG(DFATAL) << "Invalid status value: " << status;
+      return "???";
+  }
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertSynStreamFrame(
+    const net::SpdySynStreamIR& frame) {
+  if (state_ != NO_FRAMES_YET) {
+    return EXTRA_SYN_STREAM;
+  }
+  state_ = RECEIVED_SYN_STREAM;
+
+  const net::SpdyHeaderBlock& block = frame.name_value_block();
+
+  if (!GenerateRequestLine(spdy_version(), block, visitor_)) {
+    return BAD_REQUEST;
+  }
+
+  // Translate the headers to HTTP.
+  GenerateLeadingHeaders(block);
+
+  // If this is the last (i.e. only) frame on this stream, finish off the HTTP
+  // request.
+  if (frame.fin()) {
+    FinishRequest();
+  }
+
+  return SPDY_CONVERTER_SUCCESS;
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertHeadersFrame(
+    const net::SpdyHeadersIR& frame) {
+  if (state_ == RECEIVED_FLAG_FIN) {
+    return FRAME_AFTER_FIN;
+  } else if (state_ == NO_FRAMES_YET) {
+    return FRAME_BEFORE_SYN_STREAM;
+  }
+
+  // Parse the headers from the HEADERS frame.  If there have already been any
+  // data frames, then we need to save these headers for later and send them as
+  // trailing headers.  Otherwise, we can send them immediately.
+  if (state_ == RECEIVED_DATA) {
+    if (use_chunking_) {
+      const net::SpdyHeaderBlock& block = frame.name_value_block();
+      trailing_headers_.insert(block.begin(), block.end());
+    } else {
+      LOG(WARNING) << "Client sent trailing headers, "
+                   << "but we had to ignore them.";
+    }
+  } else {
+    DCHECK(state_ == RECEIVED_SYN_STREAM);
+    DCHECK(trailing_headers_.empty());
+    // Translate the headers to HTTP.
+    GenerateLeadingHeaders(frame.name_value_block());
+  }
+
+  // If this is the last frame on this stream, finish off the HTTP request.
+  if (frame.fin()) {
+    FinishRequest();
+  }
+
+  return SPDY_CONVERTER_SUCCESS;
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertDataFrame(
+    const net::SpdyDataIR& frame) {
+  if (state_ == RECEIVED_FLAG_FIN) {
+    return FRAME_AFTER_FIN;
+  } else if (state_ == NO_FRAMES_YET) {
+    return FRAME_BEFORE_SYN_STREAM;
+  }
+
+  // If this is the first data frame in the stream, we need to close the HTTP
+  // headers section (for streams where there are never any data frames, we
+  // close the headers section in FinishRequest instead).  Just before we do,
+  // we may need to add some last-minute headers.
+  if (state_ == RECEIVED_SYN_STREAM) {
+    state_ = RECEIVED_DATA;
+
+    // Unless we're not using chunked encoding (due to having received a
+    // Content-Length headers), set Transfer-Encoding: chunked now.
+    if (use_chunking_) {
+      visitor_->OnLeadingHeader(http::kTransferEncoding, http::kChunked);
+    }
+
+    // Add any other last minute headers we need, and close the leading headers
+    // section.
+    EndOfLeadingHeaders();
+  }
+  DCHECK(state_ == RECEIVED_DATA);
+
+  // Translate the SPDY data frame into an HTTP data chunk.  However, we must
+  // not emit a zero-length chunk, as that would be interpreted as the
+  // data-chunks-complete marker.
+  if (frame.data().size() > 0) {
+    if (use_chunking_) {
+      visitor_->OnDataChunk(frame.data());
+    } else {
+      visitor_->OnRawData(frame.data());
+    }
+  }
+
+  // If this is the last frame on this stream, finish off the HTTP request.
+  if (frame.fin()) {
+    FinishRequest();
+  }
+
+  return SPDY_CONVERTER_SUCCESS;
+}
+
+// Convert the given SPDY header block (e.g. from a SYN_STREAM or HEADERS
+// frame) into HTTP headers by calling OnLeadingHeader on the given visitor.
+void SpdyToHttpConverter::GenerateLeadingHeaders(
+    const net::SpdyHeaderBlock& block) {
+  for (net::SpdyHeaderBlock::const_iterator it = block.begin();
+       it != block.end(); ++it) {
+    base::StringPiece key = it->first;
+    const base::StringPiece value = it->second;
+
+    // Skip SPDY-specific (i.e. non-HTTP) headers.
+    if (spdy_version() < spdy::SPDY_VERSION_3) {
+      if (key == spdy::kSpdy2Method || key == spdy::kSpdy2Scheme ||
+          key == spdy::kSpdy2Url || key == spdy::kSpdy2Version) {
+        continue;
+      }
+    } else {
+      if (key == spdy::kSpdy3Method || key == spdy::kSpdy3Scheme ||
+          key == spdy::kSpdy3Path || key == spdy::kSpdy3Version) {
+        continue;
+      }
+    }
+
+    // Skip headers that are ignored by SPDY.
+    if (key == http::kConnection || key == http::kKeepAlive) {
+      continue;
+    }
+
+    // If the client sent a Content-Length header, take note, so that we'll
+    // know not to used chunked encoding.
+    if (key == http::kContentLength) {
+      use_chunking_ = false;
+    }
+
+    // The client shouldn't be sending us a Transfer-Encoding header; it's
+    // pretty pointless over SPDY.  If they do send one, just ignore it; we may
+    // be overriding it later anyway.
+    if (key == http::kTransferEncoding) {
+      LOG(WARNING) << "Client sent \"transfer-encoding: " << value
+                   << "\" header over SPDY.  Why would they do that?";
+      continue;
+    }
+
+    // For SPDY v3 and later, we need to convert the SPDY ":host" header to an
+    // HTTP "host" header.
+    if (spdy_version() >= spdy::SPDY_VERSION_3 && key == spdy::kSpdy3Host) {
+      key = http::kHost;
+    }
+
+    // Take note of whether the client has sent an explicit Accept-Encoding
+    // header; if they never do, we'll insert on for them later on.
+    if (key == http::kAcceptEncoding) {
+      // TODO(mdsteele): Ideally, if the client sends something like
+      //   "Accept-Encoding: lzma", we should change it to "Accept-Encoding:
+      //   lzma, gzip".  However, that's more work (we might need to parse the
+      //   syntax, to make sure we don't naively break it), and isn't
+      //   (currently) likely to come up in practice.
+      seen_accept_encoding_ = true;
+    }
+
+    InsertHeader<&HttpRequestVisitorInterface::OnLeadingHeader>(
+        key, value, visitor_);
+  }
+}
+
+void SpdyToHttpConverter::EndOfLeadingHeaders() {
+  // All SPDY clients should be assumed to support both gzip and deflate, even
+  // if they don't say so (SPDY draft 2 section 3; SPDY draft 3 section 3.2.1),
+  // and indeed some SPDY clients omit the Accept-Encoding header.  So if we
+  // didn't see that header yet, add one now so that Apache knows it can use
+  // gzip/deflate.
+  if (!seen_accept_encoding_) {
+    visitor_->OnLeadingHeader(http::kAcceptEncoding, http::kGzipDeflate);
+  }
+
+  visitor_->OnLeadingHeadersComplete();
+}
+
+void SpdyToHttpConverter::FinishRequest() {
+  if (state_ == RECEIVED_DATA) {
+    if (use_chunking_) {
+      // Indicate that there is no more data coming.
+      visitor_->OnDataChunksComplete();
+
+      // Append whatever trailing headers we've buffered, if any.
+      if (!trailing_headers_.empty()) {
+        for (net::SpdyHeaderBlock::const_iterator it =
+                 trailing_headers_.begin();
+             it != trailing_headers_.end(); ++it) {
+          InsertHeader<&HttpRequestVisitorInterface::OnTrailingHeader>(
+              it->first, it->second, visitor_);
+        }
+        trailing_headers_.clear();
+        visitor_->OnTrailingHeadersComplete();
+      }
+    } else {
+      // We don't add to trailing_headers_ if we're in no-chunk mode (we simply
+      // ignore trailing HEADERS frames), so trailing_headers_ should still be
+      // empty.
+      DCHECK(trailing_headers_.empty());
+    }
+  } else {
+    DCHECK(state_ == RECEIVED_SYN_STREAM);
+    // We only ever add to trailing_headers_ after receiving at least one data
+    // frame, so if we haven't received any data frames then trailing_headers_
+    // should still be empty.
+    DCHECK(trailing_headers_.empty());
+
+    // There were no data frames in this stream, so we haven't closed the
+    // normal (non-trailing) headers yet (if there had been any data frames, we
+    // would have closed the normal headers in ConvertDataFrame instead).  Do
+    // so now.
+    EndOfLeadingHeaders();
+  }
+
+  // Indicate that this request is finished.
+  visitor_->OnComplete();
+  state_ = RECEIVED_FLAG_FIN;
+}
+
+}  // namespace mod_spdy

Added: httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h (added)
+++ httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h Mon Apr 28 10:55:17 2014
@@ -0,0 +1,85 @@
+// Copyright 2010 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_SPDY_TO_HTTP_CONVERTER_H_
+#define MOD_SPDY_SPDY_TO_HTTP_CONVERTER_H_
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+class HttpRequestVisitorInterface;
+
+// Incrementally converts SPDY frames to HTTP streams, and passes the HTTP
+// stream to the specified HttpRequestVisitorInterface.
+class SpdyToHttpConverter {
+ public:
+  SpdyToHttpConverter(spdy::SpdyVersion spdy_version,
+                      HttpRequestVisitorInterface* visitor);
+  ~SpdyToHttpConverter();
+
+  enum Status {
+    SPDY_CONVERTER_SUCCESS,
+    FRAME_BEFORE_SYN_STREAM,  // first frame was not a SYN_STREAM
+    FRAME_AFTER_FIN,  // received another frame after a FLAG_FIN
+    EXTRA_SYN_STREAM,  // received an additional SYN_STREAM after the first
+    INVALID_HEADER_BLOCK,  // the headers could not be parsed
+    BAD_REQUEST  // the headers didn't constitute a valid HTTP request
+  };
+
+  static const char* StatusString(Status status);
+
+  // Return the SPDY version from which we are converting.
+  spdy::SpdyVersion spdy_version() const { return spdy_version_; }
+
+  // Convert the SPDY frame to HTTP and make appropriate calls to the visitor.
+  // In some cases data may be buffered, but everything will get flushed out to
+  // the visitor by the time the final frame (with FLAG_FIN set) is done.
+  Status ConvertSynStreamFrame(const net::SpdySynStreamIR& frame);
+  Status ConvertHeadersFrame(const net::SpdyHeadersIR& frame);
+  Status ConvertDataFrame(const net::SpdyDataIR& frame);
+
+private:
+  // Called to generate leading headers from a SYN_STREAM or HEADERS frame.
+  void GenerateLeadingHeaders(const net::SpdyHeaderBlock& block);
+  // Called when there are no more leading headers, because we've received
+  // either data or a FLAG_FIN.  This adds any last-minute needed headers
+  // before closing the leading headers section.
+  void EndOfLeadingHeaders();
+  // Called when we see a FLAG_FIN.  This terminates the request and appends
+  // whatever trailing headers (if any) we have buffered.
+  void FinishRequest();
+
+  enum State {
+    NO_FRAMES_YET,        // We haven't seen any frames yet.
+    RECEIVED_SYN_STREAM,  // We've seen the SYN_STREAM, but no DATA yet.
+    RECEIVED_DATA,        // We've seen at least one DATA frame.
+    RECEIVED_FLAG_FIN     // We've seen the FLAG_FIN; no more frames allowed.
+  };
+
+  const spdy::SpdyVersion spdy_version_;
+  HttpRequestVisitorInterface* const visitor_;
+  net::SpdyHeaderBlock trailing_headers_;
+  State state_;
+  bool use_chunking_;
+  bool seen_accept_encoding_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyToHttpConverter);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_SPDY_TO_HTTP_CONVERTER_H_

Propchange: httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter_test.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter_test.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter_test.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter_test.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,340 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_to_http_converter.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/http_request_visitor_interface.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using mod_spdy::SpdyToHttpConverter;
+using testing::Eq;
+using testing::InSequence;
+using testing::Sequence;
+
+const char* kMethod = "GET";
+const char* kScheme = "http";
+const char* kHost = "www.example.com";
+const char* kPath = "/";
+const char* kVersion = "HTTP/1.1";
+const char kMultiValue[] = "this\0is\0\0\0four\0\0headers";
+
+class MockHttpRequestVisitor: public mod_spdy::HttpRequestVisitorInterface {
+ public:
+  MOCK_METHOD3(OnRequestLine, void(const base::StringPiece&,
+                                   const base::StringPiece&,
+                                   const base::StringPiece&));
+  MOCK_METHOD2(OnLeadingHeader, void(const base::StringPiece&,
+                                     const base::StringPiece&));
+  MOCK_METHOD0(OnLeadingHeadersComplete, void());
+  MOCK_METHOD1(OnRawData, void(const base::StringPiece&));
+  MOCK_METHOD1(OnDataChunk, void(const base::StringPiece&));
+  MOCK_METHOD0(OnDataChunksComplete, void());
+  MOCK_METHOD2(OnTrailingHeader, void(const base::StringPiece&,
+                                      const base::StringPiece&));
+  MOCK_METHOD0(OnTrailingHeadersComplete, void());
+  MOCK_METHOD0(OnComplete, void());
+};
+
+class SpdyToHttpConverterTest :
+      public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+  SpdyToHttpConverterTest() : converter_(GetParam(), &visitor_) {}
+
+ protected:
+  void AddRequiredHeaders() {
+    if (converter_.spdy_version() < mod_spdy::spdy::SPDY_VERSION_3) {
+      headers_[mod_spdy::spdy::kSpdy2Method] = kMethod;
+      headers_[mod_spdy::spdy::kSpdy2Scheme] = kScheme;
+      headers_[mod_spdy::http::kHost] = kHost;
+      headers_[mod_spdy::spdy::kSpdy2Url] = kPath;
+      headers_[mod_spdy::spdy::kSpdy2Version] = kVersion;
+    } else {
+      headers_[mod_spdy::spdy::kSpdy3Method] = kMethod;
+      headers_[mod_spdy::spdy::kSpdy3Scheme] = kScheme;
+      headers_[mod_spdy::spdy::kSpdy3Host] = kHost;
+      headers_[mod_spdy::spdy::kSpdy3Path] = kPath;
+      headers_[mod_spdy::spdy::kSpdy3Version] = kVersion;
+    }
+  }
+
+  MockHttpRequestVisitor visitor_;
+  SpdyToHttpConverter converter_;
+  net::SpdyHeaderBlock headers_;
+};
+
+TEST_P(SpdyToHttpConverterTest, MultiFrameStream) {
+  // We expect all calls to happen in the specified order.
+  InSequence seq;
+
+  const net::SpdyStreamId stream_id = 1;
+  AddRequiredHeaders();
+
+  EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)));
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)));
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("transfer-encoding"),
+                                        Eq("chunked")));
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq(mod_spdy::http::kAcceptEncoding),
+                                        Eq(mod_spdy::http::kGzipDeflate)));
+  EXPECT_CALL(visitor_, OnLeadingHeadersComplete());
+  scoped_ptr<net::SpdySynStreamIR> syn_stream_frame(
+      new net::SpdySynStreamIR(stream_id));
+  syn_stream_frame->set_priority(1);
+  syn_stream_frame->GetMutableNameValueBlock()->insert(
+      headers_.begin(), headers_.end());
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertSynStreamFrame(*syn_stream_frame));
+
+  EXPECT_CALL(visitor_, OnDataChunk(Eq(kHost)));
+  scoped_ptr<net::SpdyDataIR> data_frame_1(
+      new net::SpdyDataIR(stream_id, kHost));
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertDataFrame(*data_frame_1));
+
+  // Should be no call to OnDataChunk for an empty data frame.
+  scoped_ptr<net::SpdyDataIR> data_frame_empty(
+      new net::SpdyDataIR(stream_id, ""));
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertDataFrame(*data_frame_empty));
+
+  EXPECT_CALL(visitor_, OnDataChunk(Eq(kVersion)));
+  EXPECT_CALL(visitor_, OnDataChunksComplete());
+  EXPECT_CALL(visitor_, OnComplete());
+  scoped_ptr<net::SpdyDataIR> data_frame_2(
+      new net::SpdyDataIR(stream_id, kVersion));
+  data_frame_2->set_fin(true);
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertDataFrame(*data_frame_2));
+}
+
+TEST_P(SpdyToHttpConverterTest, SynFrameWithHeaders) {
+  AddRequiredHeaders();
+  headers_["foo"] = "bar";
+  headers_[mod_spdy::http::kAcceptEncoding] = "deflate, gzip, lzma";
+
+  // Create a multi-valued header to verify that it's processed
+  // properly.
+  std::string multi_values(kMultiValue, sizeof(kMultiValue));
+  headers_["multi"] = multi_values;
+
+  // Also make sure "junk" headers get skipped over.
+  headers_["empty"] = std::string("\0\0\0", 3);
+
+  scoped_ptr<net::SpdySynStreamIR> syn_frame(new net::SpdySynStreamIR(1));
+  syn_frame->set_priority(1);
+  syn_frame->set_fin(true);
+  syn_frame->GetMutableNameValueBlock()->insert(
+      headers_.begin(), headers_.end());
+
+  // We expect a call to OnRequestLine(), followed by several calls to
+  // OnLeadingHeader() (the order of the calls to OnLeadingHeader() is
+  // non-deterministic so we put each in its own Sequence), followed by a final
+  // call to OnLeadingHeadersComplete() and OnComplete().
+  Sequence s1, s2, s3, s4;
+  EXPECT_CALL(visitor_,
+              OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)))
+      .InSequence(s1, s2, s3, s4);
+
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("foo"), Eq("bar")))
+      .InSequence(s1);
+
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq(mod_spdy::http::kAcceptEncoding),
+                                        Eq("deflate, gzip, lzma")))
+      .InSequence(s2);
+
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("this")))
+      .InSequence(s3);
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("is")))
+      .InSequence(s3);
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("four")))
+      .InSequence(s3);
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("headers")))
+      .InSequence(s3);
+
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)))
+      .InSequence(s4);
+
+  EXPECT_CALL(visitor_, OnLeadingHeadersComplete()).InSequence(s1, s2, s3, s4);
+
+  EXPECT_CALL(visitor_, OnComplete()).InSequence(s1, s2, s3, s4);
+
+  // Trigger the calls to the mock object by passing the frame to the
+  // converter.
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertSynStreamFrame(*syn_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, TrailingHeaders) {
+  // First, send a SYN_STREAM frame without FLAG_FIN set.  We should get the
+  // headers out that we sent, but no call yet to OnLeadingHeadersComplete,
+  // because there might still be a HEADERS frame.
+  AddRequiredHeaders();
+  headers_["foo"] = "bar";
+  scoped_ptr<net::SpdySynStreamIR> syn_frame(new net::SpdySynStreamIR(1));
+  syn_frame->set_priority(1);
+  syn_frame->GetMutableNameValueBlock()->insert(
+      headers_.begin(), headers_.end());
+
+  Sequence s1, s2;
+  EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)))
+      .InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("foo"), Eq("bar")))
+      .InSequence(s1);
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)))
+      .InSequence(s2);
+
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertSynStreamFrame(*syn_frame));
+
+  // Next, send a DATA frame.  This should trigger the accept-encoding and
+  // transfer-encoding headers, and the end of the leading headers (along with
+  // the data itself, of course).
+  scoped_ptr<net::SpdyDataIR> data_frame(
+      new net::SpdyDataIR(1, "Hello, world!\n"));
+
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("transfer-encoding"),
+                                        Eq("chunked"))).InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnLeadingHeader(
+      Eq(mod_spdy::http::kAcceptEncoding),
+      Eq(mod_spdy::http::kGzipDeflate))).InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnLeadingHeadersComplete()).InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnDataChunk(Eq("Hello, world!\n"))).InSequence(s1, s2);
+
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertDataFrame(*data_frame));
+
+  // Finally, send a HEADERS frame with FLAG_FIN set.  Since this is the end of
+  // the stream, we should get out a trailing header and the HTTP stream should
+  // be closed.
+  headers_.clear();
+  headers_["quux"] = "baz";
+  scoped_ptr<net::SpdyHeadersIR> headers_frame(new net::SpdyHeadersIR(1));
+  headers_frame->set_fin(true);
+  headers_frame->GetMutableNameValueBlock()->insert(
+      headers_.begin(), headers_.end());
+
+  EXPECT_CALL(visitor_, OnDataChunksComplete()).InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnTrailingHeader(Eq("quux"), Eq("baz")))
+      .InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnTrailingHeadersComplete()).InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnComplete()).InSequence(s1, s2);
+
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, WithContentLength) {
+  // First, send a SYN_STREAM frame without FLAG_FIN set.  We should get the
+  // headers out that we sent, but no call yet to OnLeadingHeadersComplete,
+  // because there might still be a HEADERS frame.
+  AddRequiredHeaders();
+  headers_["content-length"] = "11";
+  scoped_ptr<net::SpdySynStreamIR> syn_frame(new net::SpdySynStreamIR(1));
+  syn_frame->set_priority(1);
+  syn_frame->GetMutableNameValueBlock()->insert(
+      headers_.begin(), headers_.end());
+
+  Sequence s1, s2;
+  EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)))
+      .InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("content-length"), Eq("11")))
+      .InSequence(s1);
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)))
+      .InSequence(s2);
+
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertSynStreamFrame(*syn_frame));
+
+  // Next, send a DATA frame.  This should trigger the end of the leading
+  // headers (along with the data itself, of course), but because we sent a
+  // content-length, the data should not be chunked.
+  scoped_ptr<net::SpdyDataIR> data_frame(
+      new net::SpdyDataIR(1, "foobar=quux"));
+
+  EXPECT_CALL(visitor_, OnLeadingHeader(
+      Eq(mod_spdy::http::kAcceptEncoding),
+      Eq(mod_spdy::http::kGzipDeflate))).InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnLeadingHeadersComplete()).InSequence(s1, s2);
+  EXPECT_CALL(visitor_, OnRawData(Eq("foobar=quux"))).InSequence(s1, s2);
+
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertDataFrame(*data_frame));
+
+  // Finally, send a HEADERS frame with FLAG_FIN set.  Since we're not chunking
+  // this stream, the trailing headers should be ignored.
+  headers_.clear();
+  headers_["x-metadata"] = "baz";
+  scoped_ptr<net::SpdyHeadersIR> headers_frame(new net::SpdyHeadersIR(1));
+  headers_frame->set_fin(true);
+  headers_frame->GetMutableNameValueBlock()->insert(
+      headers_.begin(), headers_.end());
+
+  EXPECT_CALL(visitor_, OnComplete()).InSequence(s1, s2);
+
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, DoubleSynStreamFrame) {
+  AddRequiredHeaders();
+  scoped_ptr<net::SpdySynStreamIR> syn_frame(
+      new net::SpdySynStreamIR(1));
+  syn_frame->set_priority(1);
+  syn_frame->set_fin(true);
+  syn_frame->GetMutableNameValueBlock()->insert(
+      headers_.begin(), headers_.end());
+
+  InSequence seq;
+  EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)));
+  EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)));
+  EXPECT_CALL(visitor_, OnLeadingHeader(
+      Eq(mod_spdy::http::kAcceptEncoding),
+      Eq(mod_spdy::http::kGzipDeflate)));
+  EXPECT_CALL(visitor_, OnLeadingHeadersComplete());
+  EXPECT_CALL(visitor_, OnComplete());
+
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertSynStreamFrame(*syn_frame));
+  EXPECT_EQ(SpdyToHttpConverter::EXTRA_SYN_STREAM,
+            converter_.ConvertSynStreamFrame(*syn_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, HeadersFrameBeforeSynStreamFrame) {
+  headers_["x-foo"] = "bar";
+  scoped_ptr<net::SpdyHeadersIR> headers_frame(new net::SpdyHeadersIR(1));
+  headers_frame->GetMutableNameValueBlock()->insert(
+      headers_.begin(), headers_.end());
+  EXPECT_EQ(SpdyToHttpConverter::FRAME_BEFORE_SYN_STREAM,
+            converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, DataFrameBeforeSynStreamFrame) {
+  scoped_ptr<net::SpdyDataIR> data_frame(
+      new net::SpdyDataIR(1, kHost));
+  EXPECT_EQ(SpdyToHttpConverter::FRAME_BEFORE_SYN_STREAM,
+            converter_.ConvertDataFrame(*data_frame));
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, SpdyToHttpConverterTest, testing::Values(
+    mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+    mod_spdy::spdy::SPDY_VERSION_3_1));
+
+}  // namespace

Added: httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,73 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/testing/async_task_runner.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "mod_spdy/common/executor.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/thread_pool.h"
+#include "net/instaweb/util/public/function.h"
+
+namespace mod_spdy {
+
+namespace testing {
+
+namespace {
+
+class TaskFunction : public net_instaweb::Function {
+ public:
+  TaskFunction(AsyncTaskRunner::Task* task, Notification* done)
+      : task_(task), done_(done) {}
+  virtual ~TaskFunction() {}
+ protected:
+  // net_instaweb::Function methods:
+  virtual void Run() {
+    task_->Run();
+    done_->Set();
+  }
+  virtual void Cancel() {}
+ private:
+  AsyncTaskRunner::Task* const task_;
+  Notification* const done_;
+  DISALLOW_COPY_AND_ASSIGN(TaskFunction);
+};
+
+}  // namespace
+
+AsyncTaskRunner::Task::Task() {}
+
+AsyncTaskRunner::Task::~Task() {}
+
+AsyncTaskRunner::AsyncTaskRunner(Task* task)
+    : task_(task), thread_pool_(1, 1) {}
+
+AsyncTaskRunner::~AsyncTaskRunner() {}
+
+bool AsyncTaskRunner::Start() {
+  // Make sure we haven't started yet.
+  DCHECK(executor_ == NULL);
+
+  if (!thread_pool_.Start()) {
+    return false;
+  }
+  executor_.reset(thread_pool_.NewExecutor());
+  executor_->AddTask(new TaskFunction(task_.get(), &notification_), 0);
+  return true;
+}
+
+}  // namespace testing
+
+}  // namespace mod_spdy

Added: httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h Mon Apr 28 10:55:17 2014
@@ -0,0 +1,81 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_
+#define MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/thread_pool.h"
+
+namespace mod_spdy {
+
+class Executor;
+
+namespace testing {
+
+// An AsyncTaskRunner is a testing utility class to make it very easy to run a
+// single piece of code concurrently, in order to write tests for concurrency
+// features of other classes.  Standard usage is:
+//
+//   class FooTask : public AsyncTaskRunner::Task { ... };
+//
+//   AsyncTaskRunner runner(new FooTask(...));
+//   ASSERT_TRUE(runner.Start());
+//   ... stuff goes here ...
+//   runner.notification()->ExpectSetWithin(...);  // or whatever
+//
+// Note that the implementation of this class is not particularly efficient,
+// and is suitable only for testing purposes.
+class AsyncTaskRunner {
+ public:
+  // A closure to be run by the AsyncTaskRunner.  If we had a simple closure
+  // class available already, we'd use that instead.
+  class Task {
+   public:
+    Task();
+    virtual ~Task();
+    virtual void Run() = 0;
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Task);
+  };
+
+  // Construct an AsyncTaskRunner that will run the given task once started.
+  // The AsyncTaskRunner gains ownership of the task.
+  explicit AsyncTaskRunner(Task* task);
+
+  ~AsyncTaskRunner();
+
+  // Start the task running and return true.  If this fails, it returns false,
+  // and the test should be aborted.
+  bool Start();
+
+  // Get the notification that will be set when the task completes.
+  Notification* notification() { return &notification_; }
+
+ private:
+  const scoped_ptr<Task> task_;
+  mod_spdy::ThreadPool thread_pool_;
+  scoped_ptr<Executor> executor_;
+  Notification notification_;
+
+  DISALLOW_COPY_AND_ASSIGN(AsyncTaskRunner);
+};
+
+}  // namespace testing
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_

Propchange: httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/httpd/trunk/modules/spdy/common/testing/notification.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/notification.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/notification.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/notification.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,66 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/testing/notification.h"
+
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mod_spdy {
+
+namespace testing {
+
+Notification::Notification() : condvar_(&lock_), is_set_(false) {}
+
+Notification::~Notification() {
+  Set();
+}
+
+void Notification::Set() {
+  base::AutoLock autolock(lock_);
+  is_set_ = true;
+  condvar_.Broadcast();
+}
+
+void Notification::Wait() {
+  base::AutoLock autolock(lock_);
+  while (!is_set_) {
+    condvar_.Wait();
+  }
+}
+
+void Notification::ExpectNotSet() {
+  base::AutoLock autolock(lock_);
+  EXPECT_FALSE(is_set_);
+}
+
+void Notification::ExpectSetWithin(const base::TimeDelta& timeout) {
+  base::AutoLock autolock(lock_);
+  const base::TimeDelta zero = base::TimeDelta();
+  base::TimeDelta time_remaining = timeout;
+  while (time_remaining > zero && !is_set_) {
+    const base::TimeTicks start = base::TimeTicks::HighResNow();
+    condvar_.TimedWait(time_remaining);
+    time_remaining -= base::TimeTicks::HighResNow() - start;
+  }
+  EXPECT_TRUE(is_set_);
+}
+
+void Notification::ExpectSetWithinMillis(int millis) {
+  ExpectSetWithin(base::TimeDelta::FromMilliseconds(millis));
+}
+
+}  // namespace testing
+
+}  // namespace mod_spdy

Added: httpd/httpd/trunk/modules/spdy/common/testing/notification.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/notification.h?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/notification.h (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/notification.h Mon Apr 28 10:55:17 2014
@@ -0,0 +1,59 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_TESTING_NOTIFICATION_H_
+#define MOD_SPDY_COMMON_TESTING_NOTIFICATION_H_
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace base { class TimeDelta; }
+
+namespace mod_spdy {
+
+namespace testing {
+
+// A Notification allows one thread to Wait() until another thread calls Set()
+// at least once.  In order to help avoid deadlock, Set() is also called by the
+// destructor.
+class Notification {
+ public:
+  Notification();
+  ~Notification();
+
+  // Set the notification.
+  void Set();
+  // Block until the notification is set.
+  void Wait();
+  // In a unit test, expect that the notification has not yet been set.
+  void ExpectNotSet();
+  // In a unit test, expect that the notification is currently set, or becomes
+  // set by another thread within the give time delta.
+  void ExpectSetWithin(const base::TimeDelta& delay);
+  void ExpectSetWithinMillis(int millis);
+
+ private:
+  base::Lock lock_;
+  base::ConditionVariable condvar_;
+  bool is_set_;
+
+  DISALLOW_COPY_AND_ASSIGN(Notification);
+};
+
+}  // namespace testing
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_TESTING_NOTIFICATION_H_

Propchange: httpd/httpd/trunk/modules/spdy/common/testing/notification.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,271 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+
+#include <iostream>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/stringprintf.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace {
+
+void AppendHeadersString(const net::SpdyNameValueBlock& headers,
+                         std::string* out) {
+  out->append("{ ");
+  bool comma = false;
+  for (net::SpdyNameValueBlock::const_iterator iter = headers.begin();
+       iter != headers.end(); ++iter) {
+    if (comma) {
+      out->append(", ");
+    }
+    base::StringAppendF(out, "'%s': '%s'", iter->first.c_str(),
+                        iter->second.c_str());
+    comma = true;
+  }
+  out->append(" }");
+}
+
+class FrameToStringVisitor : public net::SpdyFrameVisitor {
+ public:
+  explicit FrameToStringVisitor(std::string* out)
+      : out_(out) {
+    CHECK(out_);
+  }
+  virtual ~FrameToStringVisitor() {}
+
+  virtual void VisitSynStream(const net::SpdySynStreamIR& syn_stream) {
+    // TODO(mdsteele): include other fields
+    base::StringAppendF(
+        out_, "SYN_STREAM(%u p%u%s%s)",
+        static_cast<unsigned>(syn_stream.stream_id()),
+        static_cast<unsigned>(syn_stream.priority()),
+        (syn_stream.fin() ? " fin" : ""),
+        (syn_stream.unidirectional() ? " unidirectional" : ""));
+    AppendHeadersString(syn_stream.name_value_block(), out_);
+  }
+  virtual void VisitSynReply(const net::SpdySynReplyIR& syn_reply) {
+    base::StringAppendF(
+        out_, "SYN_REPLY(%u%s)",
+        static_cast<unsigned>(syn_reply.stream_id()),
+        (syn_reply.fin() ? " fin" : ""));
+    AppendHeadersString(syn_reply.name_value_block(), out_);
+  }
+  virtual void VisitRstStream(const net::SpdyRstStreamIR& rst_stream) {
+    base::StringAppendF(
+        out_, "RST_STREAM(%u %s)",
+        static_cast<unsigned>(rst_stream.stream_id()),
+        mod_spdy::RstStreamStatusCodeToString(rst_stream.status()));
+  }
+  virtual void VisitSettings(const net::SpdySettingsIR& settings) {
+    base::StringAppendF(
+        out_, "SETTINGS(%s",
+        (settings.clear_settings() ? "clear " : ""));
+    bool comma = false;
+    for (net::SpdySettingsIR::ValueMap::const_iterator iter =
+             settings.values().begin(), end = settings.values().end();
+         iter != end; ++iter) {
+      if (comma) {
+        out_->append(", ");
+      }
+      base::StringAppendF(
+          out_, "%s%s%s: %d",
+          (iter->second.persist_value ? "persist " : ""),
+          (iter->second.persisted ? "persisted " : ""),
+          mod_spdy::SettingsIdToString(iter->first),
+          static_cast<int>(iter->second.value));
+    }
+    out_->append(")");
+  }
+  virtual void VisitPing(const net::SpdyPingIR& ping) {
+    base::StringAppendF(
+        out_, "PING(%u)", static_cast<unsigned>(ping.id()));
+  }
+  virtual void VisitGoAway(const net::SpdyGoAwayIR& goaway) {
+    base::StringAppendF(
+        out_, "GOAWAY(%u %s)",
+        static_cast<unsigned>(goaway.last_good_stream_id()),
+        mod_spdy::GoAwayStatusCodeToString(goaway.status()));
+  }
+  virtual void VisitHeaders(const net::SpdyHeadersIR& headers) {
+    base::StringAppendF(
+        out_, "HEADERS(%u%s)", static_cast<unsigned>(headers.stream_id()),
+        (headers.fin() ? " fin" : ""));
+    AppendHeadersString(headers.name_value_block(), out_);
+  }
+  virtual void VisitWindowUpdate(const net::SpdyWindowUpdateIR& window) {
+    base::StringAppendF(
+        out_, "WINDOW_UPDATE(%u %+d)",
+        static_cast<unsigned>(window.stream_id()),
+        static_cast<int>(window.delta()));
+  }
+  virtual void VisitCredential(const net::SpdyCredentialIR& credential) {
+    // TODO(mdsteele): include other fields
+    base::StringAppendF(
+        out_, "CREDENTIAL(%d)", static_cast<int>(credential.slot()));
+  }
+  virtual void VisitBlocked(const net::SpdyBlockedIR& blocked) {
+    base::StringAppendF(
+        out_, "BLOCKED(%u)", static_cast<unsigned>(blocked.stream_id()));
+  }
+  virtual void VisitPushPromise(const net::SpdyPushPromiseIR& push_promise) {
+    base::StringAppendF(
+        out_, "PUSH_PROMISE(%u, %u)",
+        static_cast<unsigned>(push_promise.stream_id()),
+        static_cast<unsigned>(push_promise.promised_stream_id()));
+  }
+  virtual void VisitData(const net::SpdyDataIR& data) {
+    base::StringAppendF(
+        out_, "DATA(%u%s \"", static_cast<unsigned>(data.stream_id()),
+        (data.fin() ? " fin" : ""));
+    out_->append(data.data().data(), data.data().size());
+    out_->append("\")");
+  }
+
+ private:
+  std::string* out_;
+
+  DISALLOW_COPY_AND_ASSIGN(FrameToStringVisitor);
+};
+
+void AppendSpdyFrameToString(const net::SpdyFrameIR& frame, std::string* out) {
+  FrameToStringVisitor visitor(out);
+  frame.Visit(&visitor);
+}
+
+class IsEquivalentFrameMatcher :
+      public ::testing::MatcherInterface<const net::SpdyFrameIR&> {
+ public:
+  explicit IsEquivalentFrameMatcher(const net::SpdyFrameIR& frame);
+  virtual ~IsEquivalentFrameMatcher();
+  virtual bool MatchAndExplain(const net::SpdyFrameIR& frame,
+                               ::testing::MatchResultListener* listener) const;
+  virtual void DescribeTo(std::ostream* out) const;
+  virtual void DescribeNegationTo(std::ostream* out) const;
+
+ private:
+  std::string expected_;
+
+  DISALLOW_COPY_AND_ASSIGN(IsEquivalentFrameMatcher);
+};
+
+IsEquivalentFrameMatcher::IsEquivalentFrameMatcher(
+    const net::SpdyFrameIR& frame) {
+  AppendSpdyFrameToString(frame, &expected_);
+}
+
+IsEquivalentFrameMatcher::~IsEquivalentFrameMatcher() {}
+
+bool IsEquivalentFrameMatcher::MatchAndExplain(
+    const net::SpdyFrameIR& frame,
+    ::testing::MatchResultListener* listener) const {
+  std::string actual;
+  AppendSpdyFrameToString(frame, &actual);
+  if (actual != expected_) {
+    *listener << "is a " << actual << " frame";
+    return false;
+  }
+  return true;
+}
+
+void IsEquivalentFrameMatcher::DescribeTo(std::ostream* out) const {
+  *out << "is a " << expected_ << " frame";
+}
+
+void IsEquivalentFrameMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "isn't a " << expected_ << " frame";
+}
+
+}  // namespace
+
+namespace mod_spdy {
+
+namespace testing {
+
+::testing::Matcher<const net::SpdyFrameIR&> IsSynStream(
+     net::SpdyStreamId stream_id, net::SpdyStreamId assoc_stream_id,
+     net::SpdyPriority priority, bool fin, bool unidirectional,
+     const net::SpdyNameValueBlock& headers) {
+  net::SpdySynStreamIR frame(stream_id);
+  frame.set_associated_to_stream_id(assoc_stream_id);
+  frame.set_priority(priority);
+  frame.set_fin(fin);
+  frame.set_unidirectional(unidirectional);
+  frame.GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+  return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsSynReply(
+     net::SpdyStreamId stream_id, bool fin,
+     const net::SpdyNameValueBlock& headers) {
+  net::SpdySynReplyIR frame(stream_id);
+  frame.set_fin(fin);
+  frame.GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+  return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsRstStream(
+     net::SpdyStreamId stream_id, net::SpdyRstStreamStatus status) {
+  net::SpdyRstStreamIR frame(stream_id, status);
+  return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsSettings(
+     net::SpdySettingsIds id, int32 value) {
+  net::SpdySettingsIR frame;
+  frame.AddSetting(id, false, false, value);
+  return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsPing(net::SpdyPingId ping_id) {
+  net::SpdyPingIR frame(ping_id);
+  return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsGoAway(
+     net::SpdyStreamId last_good_stream_id, net::SpdyGoAwayStatus status) {
+  net::SpdyGoAwayIR frame(last_good_stream_id, status);
+  return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsHeaders(
+     net::SpdyStreamId stream_id, bool fin,
+     const net::SpdyNameValueBlock& headers) {
+  net::SpdyHeadersIR frame(stream_id);
+  frame.set_fin(fin);
+  frame.GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+  return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsWindowUpdate(
+     net::SpdyStreamId stream_id, uint32 delta) {
+  net::SpdyWindowUpdateIR frame(stream_id, delta);
+  return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsDataFrame(
+    net::SpdyStreamId stream_id, bool fin, base::StringPiece payload) {
+  net::SpdyDataIR frame(stream_id, payload);
+  frame.set_fin(fin);
+  return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+}  // namespace testing
+
+}  // namespace mod_spdy

Added: httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h Mon Apr 28 10:55:17 2014
@@ -0,0 +1,79 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_
+#define MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace mod_spdy {
+
+namespace testing {
+
+// Make a matcher that requires the argument to be a SYN_STREAM frame with the
+// given stream ID, associated stream ID, priority, flag_fin,
+// flag_unidirectional, and headers.
+::testing::Matcher<const net::SpdyFrameIR&> IsSynStream(
+     net::SpdyStreamId stream_id, net::SpdyStreamId assoc_stream_id,
+     net::SpdyPriority priority, bool fin, bool unidirectional,
+     const net::SpdyNameValueBlock& headers);
+
+// Make a matcher that requires the argument to be a SYN_REPLY frame with the
+// given stream ID, flag_fin, and headers.
+::testing::Matcher<const net::SpdyFrameIR&> IsSynReply(
+     net::SpdyStreamId stream_id, bool fin,
+     const net::SpdyNameValueBlock& headers);
+
+// Make a matcher that requires the argument to be a RST_STREAM frame with the
+// given stream ID and status code.
+::testing::Matcher<const net::SpdyFrameIR&> IsRstStream(
+     net::SpdyStreamId stream_id, net::SpdyRstStreamStatus status);
+
+// Make a matcher that requires the argument to be a SETTINGS frame with the
+// given setting.
+::testing::Matcher<const net::SpdyFrameIR&> IsSettings(
+     net::SpdySettingsIds id, int32 value);
+
+// Make a matcher that requires the argument to be a PING frame with the
+// given ID.
+::testing::Matcher<const net::SpdyFrameIR&> IsPing(net::SpdyPingId ping_id);
+
+// Make a matcher that requires the argument to be a GOAWAY frame with the
+// given last-good-stream-ID and status code.
+::testing::Matcher<const net::SpdyFrameIR&> IsGoAway(
+     net::SpdyStreamId last_good_stream_id, net::SpdyGoAwayStatus status);
+
+// Make a matcher that requires the argument to be a HEADERS frame with the
+// given stream ID, flag_fin, and headers.
+::testing::Matcher<const net::SpdyFrameIR&> IsHeaders(
+     net::SpdyStreamId stream_id, bool fin,
+     const net::SpdyNameValueBlock& headers);
+
+// Make a matcher that requires the argument to be a WINDOW_UPDATE frame with
+// the given window-size-delta.
+::testing::Matcher<const net::SpdyFrameIR&> IsWindowUpdate(
+     net::SpdyStreamId stream_id, uint32 delta);
+
+// Make a matcher that requires the argument to be a DATA frame.
+::testing::Matcher<const net::SpdyFrameIR&> IsDataFrame(
+     net::SpdyStreamId stream_id, bool fin, base::StringPiece payload);
+
+}  // namespace testing
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_

Propchange: httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h
------------------------------------------------------------------------------
    svn:eol-style = native