httpd-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j..@apache.org
Subject svn commit: r1591622 [13/33] - in /httpd/mod_spdy/trunk: ./ base/ base/base.xcodeproj/ base/metrics/ build/ build/all.xcodeproj/ build/build_util.xcodeproj/ build/install.xcodeproj/ build/internal/ build/linux/ build/mac/ build/util/ build/win/ install...
Date Thu, 01 May 2014 11:43:45 GMT
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_stream_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_stream_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_stream_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_stream_test.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,467 @@
+// 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/string_piece.h"
+#include "base/time.h"
+#include "mod_spdy/common/protocol_util.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/buffered_spdy_framer.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::FlagFinIs;
+using mod_spdy::testing::IsDataFrameWith;
+using mod_spdy::testing::IsRstStream;
+using mod_spdy::testing::IsWindowUpdate;
+using mod_spdy::testing::StreamIdIs;
+using testing::AllOf;
+
+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::SpdyHeaderBlock& 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::SpdyFrame* raw_frame;
+  ASSERT_TRUE(output_queue->BlockingPop(
+      base::TimeDelta::FromMilliseconds(100), &raw_frame));
+  scoped_ptr<net::SpdyFrame> frame(raw_frame);
+  EXPECT_THAT(*frame, AllOf(IsDataFrameWith(data), StreamIdIs(kStreamId),
+                            FlagFinIs(flag_fin)));
+}
+
+// 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::SpdyStatusCodes status) {
+  net::SpdyFrame* raw_frame;
+  ASSERT_TRUE(output_queue->BlockingPop(
+      base::TimeDelta::FromMilliseconds(100), &raw_frame));
+  scoped_ptr<net::SpdyFrame> frame(raw_frame);
+  EXPECT_THAT(*frame, AllOf(IsRstStream(status), StreamIdIs(kStreamId)));
+}
+
+// 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::SpdyFrame* raw_frame;
+  ASSERT_TRUE(output_queue->BlockingPop(
+      base::TimeDelta::FromMilliseconds(100), &raw_frame));
+  scoped_ptr<net::SpdyFrame> frame(raw_frame);
+  EXPECT_THAT(*frame, AllOf(IsWindowUpdate(delta), StreamIdIs(kStreamId)));
+}
+
+// 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) {
+  net::BufferedSpdyFramer framer(2);
+  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,
+      &framer, &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 v3.
+TEST(SpdyStreamTest, HasFlowControlInSpdy3) {
+  net::BufferedSpdyFramer framer(3);
+  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,
+      &framer, &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 flow control is well-behaved when the stream is aborted.
+TEST(SpdyStreamTest, FlowControlAbort) {
+  net::BufferedSpdyFramer framer(3);
+  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,
+      &framer, &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::PROTOCOL_ERROR);
+  EXPECT_TRUE(stream.is_aborted());
+  ExpectRstStream(&output_queue, net::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::SpdyHeaderBlock 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) {
+  net::BufferedSpdyFramer framer(3);
+  mod_spdy::SpdyFramePriorityQueue output_queue;
+  MockSpdyServerPushInterface pusher;
+  mod_spdy::SpdyStream stream(
+      mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+      kInitServerPushDepth, kPriority, 0x60000000, &output_queue, &framer,
+      &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::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) {
+  net::BufferedSpdyFramer framer(3);
+  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,
+      &framer, &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) {
+  net::BufferedSpdyFramer framer(2);
+  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, &framer, &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) {
+  net::BufferedSpdyFramer framer(3);
+  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,
+      &framer, &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, InputFlowControl) {
+  net::BufferedSpdyFramer framer(3);
+  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, &framer, &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";
+  stream.PostInputFrame(framer.CreateSynStream(
+        kStreamId, kAssocStreamId, kPriority,
+        0,  // 0 = no credential slot
+        net::CONTROL_FLAG_NONE,
+        false,  // false = uncompressed
+        &request_headers));
+  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(framer.CreateDataFrame(
+      kStreamId, data1.data(), data1.size(), net::DATA_FLAG_NONE));
+  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');
+  stream.PostInputFrame(framer.CreateDataFrame(
+      kStreamId, data2.data(), data2.size(), net::DATA_FLAG_FIN));
+  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, InputFlowControlError) {
+  net::BufferedSpdyFramer framer(3);
+  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, &framer, &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(framer.CreateDataFrame(
+        kStreamId, data1.data(), data1.size(), net::DATA_FLAG_NONE));
+    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(framer.CreateDataFrame(
+      kStreamId, data2.data(), data2.size(), net::DATA_FLAG_NONE));
+  ExpectRstStream(&output_queue, net::FLOW_CONTROL_ERROR);
+  EXPECT_TRUE(output_queue.IsEmpty());
+  EXPECT_TRUE(stream.is_aborted());
+}
+
+TEST(SpdyStreamTest, NoInputFlowControlInSpdy2) {
+  net::BufferedSpdyFramer framer(2);
+  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, &framer, &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(framer.CreateDataFrame(
+        kStreamId, data1.data(), data1.size(), net::DATA_FLAG_NONE));
+    EXPECT_TRUE(output_queue.IsEmpty());
+    EXPECT_FALSE(stream.is_aborted());
+  }
+}
+
+}  // namespace

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,362 @@
+// 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/string_number_conversions.h"  // for Int64ToString
+#include "base/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 {
+
+// Functions to test for FLAG_FIN.  Using these functions instead of testing
+// flags() directly helps guard against mixing up & with && or mixing up
+// CONTROL_FLAG_FIN with DATA_FLAG_FIN.
+bool HasControlFlagFinSet(const net::SpdyControlFrame& frame) {
+  return bool(frame.flags() & net::CONTROL_FLAG_FIN);
+}
+bool HasDataFlagFinSet(const net::SpdyDataFrame& frame) {
+  return bool(frame.flags() & net::DATA_FLAG_FIN);
+}
+
+// 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),
+      framer_(SpdyVersionToFramerVersion(spdy_version)),
+      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::SpdySynStreamControlFrame& frame) {
+  if (state_ != NO_FRAMES_YET) {
+    return EXTRA_SYN_STREAM;
+  }
+  state_ = RECEIVED_SYN_STREAM;
+
+  net::SpdyHeaderBlock block;
+  if (!framer_.ParseHeaderBlockInBuffer(
+          frame.header_block(), frame.header_block_len(), &block)) {
+    return INVALID_HEADER_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 (HasControlFlagFinSet(frame)) {
+    FinishRequest();
+  }
+
+  return SPDY_CONVERTER_SUCCESS;
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertHeadersFrame(
+    const net::SpdyHeadersControlFrame& 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_) {
+      if (!framer_.ParseHeaderBlockInBuffer(
+              frame.header_block(), frame.header_block_len(),
+              &trailing_headers_)) {
+        return INVALID_HEADER_BLOCK;
+      }
+    } else {
+      LOG(WARNING) << "Client sent trailing headers, "
+                   << "but we had to ignore them.";
+    }
+  } else {
+    DCHECK(state_ == RECEIVED_SYN_STREAM);
+    DCHECK(trailing_headers_.empty());
+    net::SpdyHeaderBlock block;
+    if (!framer_.ParseHeaderBlockInBuffer(
+            frame.header_block(), frame.header_block_len(), &block)) {
+      return INVALID_HEADER_BLOCK;
+    }
+
+    // Translate the headers to HTTP.
+    GenerateLeadingHeaders(block);
+  }
+
+  // If this is the last frame on this stream, finish off the HTTP request.
+  if (HasControlFlagFinSet(frame)) {
+    FinishRequest();
+  }
+
+  return SPDY_CONVERTER_SUCCESS;
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertDataFrame(
+    const net::SpdyDataFrame& 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.length() > 0) {
+    const base::StringPiece data(frame.payload(), frame.length());
+    if (use_chunking_) {
+      visitor_->OnDataChunk(data);
+    } else {
+      visitor_->OnRawData(data);
+    }
+  }
+
+  // If this is the last frame on this stream, finish off the HTTP request.
+  if (HasDataFlagFinSet(frame)) {
+    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/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter.h Thu May  1 11:43:36 2014
@@ -0,0 +1,87 @@
+// 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_framer.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::SpdySynStreamControlFrame& frame);
+  Status ConvertHeadersFrame(const net::SpdyHeadersControlFrame& frame);
+  Status ConvertDataFrame(const net::SpdyDataFrame& 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::SpdyFramer framer_;
+  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/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_to_http_converter_test.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,382 @@
+// 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/string_piece.h"
+#include "mod_spdy/common/http_request_visitor_interface.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"
+#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_),
+      framer_(mod_spdy::SpdyVersionToFramerVersion(GetParam())) {}
+
+ 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::SpdyFramer framer_;
+  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::SpdySynStreamControlFrame> syn_stream_frame(
+      framer_.CreateSynStream(
+          stream_id,
+          0,  // associated stream ID
+          1,  // priority
+          0,  // credential slot
+          net::CONTROL_FLAG_NONE,  // flags
+          false,  // use compression
+          &headers_));
+  EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+            converter_.ConvertSynStreamFrame(*syn_stream_frame));
+
+  EXPECT_CALL(visitor_, OnDataChunk(Eq(kHost)));
+  scoped_ptr<net::SpdyDataFrame> data_frame_1(
+      framer_.CreateDataFrame(
+          stream_id, kHost, strlen(kHost), net::DATA_FLAG_NONE));
+  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::SpdyDataFrame> data_frame_empty(
+      framer_.CreateDataFrame(
+          stream_id, "", 0, net::DATA_FLAG_NONE));
+  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::SpdyDataFrame> data_frame_2(
+      framer_.CreateDataFrame(
+          stream_id, kVersion, strlen(kVersion), net::DATA_FLAG_FIN));
+  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::SpdySynStreamControlFrame> syn_frame(
+      framer_.CreateSynStream(
+          1,  // stream ID
+          0,  // associated stream ID
+          1,  // priority
+          0,  // credential slot
+          net::CONTROL_FLAG_FIN,  // flags
+          false,  // use compression
+          &headers_));
+
+  // 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::SpdySynStreamControlFrame> syn_frame(
+      framer_.CreateSynStream(
+          1,  // stream ID
+          0,  // associated stream ID
+          1,  // priority
+          0,  // credential slot
+          net::CONTROL_FLAG_NONE,  // flags
+          false,  // use compression
+          &headers_));
+
+  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::SpdyDataFrame> data_frame(framer_.CreateDataFrame(
+      1,  // stream ID
+      "Hello, world!\n",  // data
+      14, // data length
+      net::DATA_FLAG_NONE));  // flags
+
+  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::SpdyHeadersControlFrame> headers_frame(
+      framer_.CreateHeaders(
+          1,  // stream ID
+          net::CONTROL_FLAG_FIN,  // flags
+          false,  // use compression
+          &headers_));
+
+  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::SpdySynStreamControlFrame> syn_frame(
+      framer_.CreateSynStream(
+          1,  // stream ID
+          0,  // associated stream ID
+          1,  // priority
+          0,  // credential slot
+          net::CONTROL_FLAG_NONE,  // flags
+          false,  // use compression
+          &headers_));
+
+  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::SpdyDataFrame> data_frame(framer_.CreateDataFrame(
+      1,  // stream ID
+      "foobar=quux",  // data
+      11, // data length
+      net::DATA_FLAG_NONE));  // flags
+
+  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::SpdyHeadersControlFrame> headers_frame(
+      framer_.CreateHeaders(
+          1,  // stream ID
+          net::CONTROL_FLAG_FIN,  // flags
+          false,  // use compression
+          &headers_));
+
+  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::SpdySynStreamControlFrame> syn_stream_frame(
+      framer_.CreateSynStream(
+          1,  // stream ID
+          0,  // associated stream ID
+          1,  // priority
+          0,  // credential slot
+          net::CONTROL_FLAG_FIN,  // flags
+          false,  // use compression
+          &headers_));
+
+  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_stream_frame));
+  EXPECT_EQ(SpdyToHttpConverter::EXTRA_SYN_STREAM,
+            converter_.ConvertSynStreamFrame(*syn_stream_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, HeadersFrameBeforeSynStreamFrame) {
+  headers_["x-foo"] = "bar";
+  scoped_ptr<net::SpdyHeadersControlFrame> headers_frame(
+      framer_.CreateHeaders(
+          1,  // stream ID
+          net::CONTROL_FLAG_NONE,  // flags
+          false,  // use compression
+          &headers_));
+  EXPECT_EQ(SpdyToHttpConverter::FRAME_BEFORE_SYN_STREAM,
+            converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, DataFrameBeforeSynStreamFrame) {
+  scoped_ptr<net::SpdyDataFrame> data_frame(
+      framer_.CreateDataFrame(
+          1,  // stream ID
+          kHost, strlen(kHost), net::DATA_FLAG_NONE));
+  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/mod_spdy/trunk/mod_spdy/common/testing/async_task_runner.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/testing/async_task_runner.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/testing/async_task_runner.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/testing/async_task_runner.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,76 @@
+// 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 "base/memory/scoped_ptr.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),
+      executor_(NULL) {}
+
+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/mod_spdy/trunk/mod_spdy/common/testing/async_task_runner.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/testing/async_task_runner.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/testing/async_task_runner.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/testing/async_task_runner.h Thu May  1 11:43:36 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/mod_spdy/trunk/mod_spdy/common/testing/async_task_runner.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/common/testing/notification.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/testing/notification.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/testing/notification.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/testing/notification.cc Thu May  1 11:43:36 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.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/mod_spdy/trunk/mod_spdy/common/testing/notification.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/testing/notification.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/testing/notification.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/testing/notification.h Thu May  1 11:43:36 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/mod_spdy/trunk/mod_spdy/common/testing/notification.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/common/testing/spdy_frame_matchers.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/testing/spdy_frame_matchers.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/testing/spdy_frame_matchers.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/testing/spdy_frame_matchers.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,444 @@
+// 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/stringprintf.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace {
+
+std::string HeadersString(const net::SpdyHeaderBlock& headers) {
+  std::string ret = "{ ";
+  bool comma = false;
+  for (net::SpdyHeaderBlock::const_iterator iter = headers.begin();
+       iter != headers.end(); ++iter) {
+    if (comma) {
+      ret += ", ";
+    }
+    ret += "'" + iter->first + "': '" + iter->second + "'";
+    comma = true;
+  }
+  ret += " }";
+  return ret;
+}
+
+}  // namespace
+
+namespace mod_spdy {
+
+namespace testing {
+
+bool IsControlFrameOfTypeMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (!frame.is_control_frame()) {
+    *listener << "is a data frame";
+    return false;
+  }
+  const net::SpdyControlFrame* ctrl_frame =
+      static_cast<const net::SpdyControlFrame*>(&frame);
+  if (ctrl_frame->type() != type_) {
+    *listener << "is a " << net::SpdyFramer::ControlTypeToString(
+        ctrl_frame->type()) << " frame";
+    return false;
+  }
+  return true;
+}
+
+void IsControlFrameOfTypeMatcher::DescribeTo(std::ostream* out) const {
+  *out << "is a " << net::SpdyFramer::ControlTypeToString(type_) << " frame";
+}
+
+void IsControlFrameOfTypeMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "isn't a " << net::SpdyFramer::ControlTypeToString(type_)
+       << " frame";
+}
+
+bool IsDataFrameMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (frame.is_control_frame()) {
+    *listener << "is a " << net::SpdyFramer::ControlTypeToString(
+        static_cast<const net::SpdyControlFrame*>(&frame)->type())
+              << " frame";
+    return false;
+  }
+  return true;
+}
+
+void IsDataFrameMatcher::DescribeTo(std::ostream* out) const {
+  *out << "is a data frame";
+}
+
+void IsDataFrameMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "isn't a data frame";
+}
+
+bool IsDataFrameWithMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (frame.is_control_frame()) {
+    *listener << "is a " << net::SpdyFramer::ControlTypeToString(
+        static_cast<const net::SpdyControlFrame*>(&frame)->type())
+              << " frame";
+    return false;
+  }
+  const base::StringPiece actual_payload(
+      static_cast<const net::SpdyDataFrame*>(&frame)->payload(),
+      frame.length());
+  if (actual_payload != payload_) {
+    *listener << "is a data frame with payload \"" << actual_payload << "\"";
+    return false;
+  }
+  return true;
+}
+
+void IsDataFrameWithMatcher::DescribeTo(std::ostream* out) const {
+  *out << "is a data frame with payload \"" << payload_ << "\"";
+}
+
+void IsDataFrameWithMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "isn't a data frame with payload \"" << payload_ << "\"";
+}
+
+bool IsGoAwayMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (!frame.is_control_frame()) {
+    *listener << "is a data frame";
+    return false;
+  }
+  const net::SpdyControlFrame* ctrl_frame =
+      static_cast<const net::SpdyControlFrame*>(&frame);
+  if (ctrl_frame->type() != net::GOAWAY) {
+    *listener << "is a " << net::SpdyFramer::ControlTypeToString(
+        ctrl_frame->type()) << " frame";
+    return false;
+  }
+  // The GOAWAY status field only exists for SPDY v3 and later, so for earlier
+  // versions just skip this check.
+  if (ctrl_frame->version() >= 3) {
+    const net::SpdyGoAwayControlFrame* go_away_frame =
+        static_cast<const net::SpdyGoAwayControlFrame*>(ctrl_frame);
+    if (go_away_frame->status() != status_) {
+      *listener << "is a GOAWAY frame with status " << go_away_frame->status();
+      return false;
+    }
+  }
+  return true;
+}
+
+void IsGoAwayMatcher::DescribeTo(std::ostream* out) const {
+  *out << "is a GOAWAY frame with status " << status_;
+}
+
+void IsGoAwayMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "isn't a GOAWAY frame with status " << status_;
+}
+
+bool IsRstStreamMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (!frame.is_control_frame()) {
+    *listener << "is a data frame";
+    return false;
+  }
+  const net::SpdyControlFrame* ctrl_frame =
+      static_cast<const net::SpdyControlFrame*>(&frame);
+  if (ctrl_frame->type() != net::RST_STREAM) {
+    *listener << "is a " << net::SpdyFramer::ControlTypeToString(
+        ctrl_frame->type()) << " frame";
+    return false;
+  }
+  const net::SpdyRstStreamControlFrame* rst_stream_frame =
+      static_cast<const net::SpdyRstStreamControlFrame*>(ctrl_frame);
+  if (rst_stream_frame->status() != status_) {
+    *listener << "is a RST_STREAM frame with status "
+              << rst_stream_frame->status();
+    return false;
+  }
+  return true;
+}
+
+void IsRstStreamMatcher::DescribeTo(std::ostream* out) const {
+  *out << "is a RST_STREAM frame with status " << status_;
+}
+
+void IsRstStreamMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "isn't a RST_STREAM frame with status " << status_;
+}
+
+bool IsWindowUpdateMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (!frame.is_control_frame()) {
+    *listener << "is a data frame";
+    return false;
+  }
+  const net::SpdyControlFrame* ctrl_frame =
+      static_cast<const net::SpdyControlFrame*>(&frame);
+  if (ctrl_frame->type() != net::WINDOW_UPDATE) {
+    *listener << "is a " << net::SpdyFramer::ControlTypeToString(
+        ctrl_frame->type()) << " frame";
+    return false;
+  }
+  const net::SpdyWindowUpdateControlFrame* window_update_frame =
+      static_cast<const net::SpdyWindowUpdateControlFrame*>(ctrl_frame);
+  if (window_update_frame->delta_window_size() != delta_) {
+    *listener << "is a WINDOW_UPDATE frame with delta="
+              << window_update_frame->delta_window_size();
+    return false;
+  }
+  return true;
+}
+
+void IsWindowUpdateMatcher::DescribeTo(std::ostream* out) const {
+  *out << "is a WINDOW_UPDATE frame with delta=" << delta_;
+}
+
+void IsWindowUpdateMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "isn't a WINDOW_UPDATE frame with delta=" << delta_;
+}
+
+bool FlagFinIsMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  const bool fin = frame.is_control_frame() ?
+      (frame.flags() & net::CONTROL_FLAG_FIN) :
+      (frame.flags() & net::DATA_FLAG_FIN);
+  if (fin != fin_) {
+    *listener << (fin ? "has FLAG_FIN set" : "doesn't have FLAG_FIN set");
+    return false;
+  }
+  return true;
+}
+
+void FlagFinIsMatcher::DescribeTo(std::ostream* out) const {
+  *out << (fin_ ? "has FLAG_FIN set" : "doesn't have FLAG_FIN set");
+}
+
+void FlagFinIsMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << (fin_ ? "doesn't have FLAG_FIN set" : "has FLAG_FIN set");
+}
+
+bool FlagUnidirectionalIsMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (!frame.is_control_frame()) {
+    *listener << "is a data frame";
+    return false;
+  }
+  const bool unidirectional =
+      (frame.flags() & net::CONTROL_FLAG_UNIDIRECTIONAL);
+  if (unidirectional != unidirectional_) {
+    *listener << (unidirectional ? "has FLAG_UNIDIRECTIONAL set" :
+                  "doesn't have FLAG_UNIDIRECTIONAL set");
+    return false;
+  }
+  return true;
+}
+
+void FlagUnidirectionalIsMatcher::DescribeTo(std::ostream* out) const {
+  *out << (unidirectional_ ? "has FLAG_UNIDIRECTIONAL set" :
+           "doesn't have FLAG_UNIDIRECTIONAL set");
+}
+
+void FlagUnidirectionalIsMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << (unidirectional_ ? "doesn't have FLAG_UNIDIRECTIONAL set" :
+           "has FLAG_UNIDIRECTIONAL set");
+}
+
+bool StreamIdIsMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  net::SpdyStreamId id;
+  if (frame.is_control_frame()) {
+    net::SpdyControlType type =
+        static_cast<const net::SpdyControlFrame*>(&frame)->type();
+    switch (type) {
+      case net::SYN_STREAM:
+        id = static_cast<const net::SpdySynStreamControlFrame*>(
+            &frame)->stream_id();
+        break;
+      case net::SYN_REPLY:
+        id = static_cast<const net::SpdySynReplyControlFrame*>(
+            &frame)->stream_id();
+        break;
+      case net::RST_STREAM:
+        id = static_cast<const net::SpdyRstStreamControlFrame*>(
+            &frame)->stream_id();
+        break;
+      case net::HEADERS:
+        id = static_cast<const net::SpdyHeadersControlFrame*>(
+            &frame)->stream_id();
+        break;
+      case net::WINDOW_UPDATE:
+        id = static_cast<const net::SpdyWindowUpdateControlFrame*>(
+            &frame)->stream_id();
+        break;
+      default:
+        *listener << "is a " << net::SpdyFramer::ControlTypeToString(type)
+                  << " frame";
+        return false;
+    }
+  } else {
+    id = static_cast<const net::SpdyDataFrame*>(&frame)->stream_id();
+  }
+  if (id != stream_id_) {
+    *listener << "has stream ID " << id;
+    return false;
+  }
+  return true;
+}
+
+void StreamIdIsMatcher::DescribeTo(std::ostream* out) const {
+  *out << "has stream ID " << stream_id_;
+}
+
+void StreamIdIsMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "doesn't have stream ID " << stream_id_;
+}
+
+bool AssociatedStreamIdIsMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (!frame.is_control_frame()) {
+    *listener << "is a data frame";
+    return false;
+  }
+  const net::SpdyControlType type =
+      static_cast<const net::SpdyControlFrame*>(&frame)->type();
+  if (type != net::SYN_STREAM) {
+    *listener << "is a " << net::SpdyFramer::ControlTypeToString(type)
+              << " frame";
+    return false;
+  }
+  const net::SpdyStreamId id =
+      static_cast<const net::SpdySynStreamControlFrame*>(
+          &frame)->associated_stream_id();
+  if (id != associated_stream_id_) {
+    *listener << "has associated stream ID " << id;
+    return false;
+  }
+  return true;
+}
+
+void AssociatedStreamIdIsMatcher::DescribeTo(std::ostream* out) const {
+  *out << "has associated stream ID " << associated_stream_id_;
+}
+
+void AssociatedStreamIdIsMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "doesn't have associated stream ID " << associated_stream_id_;
+}
+
+bool PriorityIsMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (!frame.is_control_frame()) {
+    *listener << "is a data frame";
+    return false;
+  }
+  const net::SpdyControlType type =
+      static_cast<const net::SpdyControlFrame*>(&frame)->type();
+  if (type != net::SYN_STREAM) {
+    *listener << "is a " << net::SpdyFramer::ControlTypeToString(type)
+              << " frame";
+    return false;
+  }
+  const net::SpdyPriority pri =
+      static_cast<const net::SpdySynStreamControlFrame*>(
+          &frame)->priority();
+  if (pri != priority_) {
+    *listener << "has priority " << pri;
+    return false;
+  }
+  return true;
+}
+
+void PriorityIsMatcher::DescribeTo(std::ostream* out) const {
+  *out << "has priority " << priority_;
+}
+
+void PriorityIsMatcher::DescribeNegationTo(std::ostream* out) const {
+  *out << "doesn't have priority " << priority_;
+}
+
+bool UncompressedHeadersAreMatcher::MatchAndExplain(
+    const net::SpdyFrame& frame,
+    ::testing::MatchResultListener* listener) const {
+  if (!frame.is_control_frame()) {
+    *listener << "is a data frame";
+    return false;
+  }
+  const net::SpdyControlFrame* ctrl_frame =
+      static_cast<const net::SpdyControlFrame*>(&frame);
+  const char* header_block;
+  size_t header_block_len;
+  net::SpdyControlType type = ctrl_frame->type();
+  switch (type) {
+    case net::SYN_STREAM:
+      header_block = static_cast<const net::SpdySynStreamControlFrame*>(
+          ctrl_frame)->header_block();
+      header_block_len = static_cast<const net::SpdySynStreamControlFrame*>(
+          ctrl_frame)->header_block_len();
+      break;
+    case net::SYN_REPLY:
+      header_block = static_cast<const net::SpdySynReplyControlFrame*>(
+          ctrl_frame)->header_block();
+      header_block_len = static_cast<const net::SpdySynReplyControlFrame*>(
+          ctrl_frame)->header_block_len();
+      break;
+    case net::HEADERS:
+      header_block = static_cast<const net::SpdyHeadersControlFrame*>(
+          ctrl_frame)->header_block();
+      header_block_len = static_cast<const net::SpdyHeadersControlFrame*>(
+          ctrl_frame)->header_block_len();
+      break;
+    default:
+      *listener << "is a " << net::SpdyFramer::ControlTypeToString(type)
+                << " frame";
+      return false;
+  }
+  net::SpdyFramer framer(ctrl_frame->version());
+  net::SpdyHeaderBlock actual_headers;
+  if (!framer.ParseHeaderBlockInBuffer(header_block, header_block_len,
+                                       &actual_headers)) {
+    *listener << "headers data is compressed/corrupted";
+    return false;
+  }
+  if (actual_headers != headers_) {
+    *listener << "has uncompressed headers " << HeadersString(actual_headers);
+    return false;
+  }
+  return true;
+}
+
+void UncompressedHeadersAreMatcher::DescribeTo(std::ostream* out) const {
+  *out << "has uncompressed headers " << HeadersString(headers_);
+}
+
+void UncompressedHeadersAreMatcher::DescribeNegationTo(
+    std::ostream* out) const {
+  *out << "doesn't have uncompressed headers " << HeadersString(headers_);
+}
+
+}  // namespace testing
+
+}  // namespace mod_spdy



Mime
View raw message